Наблюдатели моделей в Laravel

Наблюдатели моделей в Laravel

Сразу к практике - посмотрим где и как можно использовать наблюдателей.

Итак, предположим у нас есть модель категорий с несколькими свойствами:

  • названием title
  • так называемым slug, т.е. псевдонимом для url, то бишь адрес в строке браузера будет выглядеть как mysite.com/categories/laravel, где laravel - и есть тот самый псевдоним
  • булевым published, которое указывает, опубликована ли данная категория

Если все поля прописаны в формах создания новой и обновления существующей категорий, то соответствующие методы в моём контроллере выглядели бы примерно  так:

class CategoryController extends Controller
{
    public function store(Request $request)
    {
        $category = Category::create($request->only($this->getEditableColumns()));
        ...
    }

    public function update(Request $request, Category $category)
    {
        $category->update($request->only($this->getEditableColumns()));
        ...
    }

    protected function getEditableColumns()
    {
        return ['title', 'slug', 'published', 'sort_order'];
    }
}

(валидацию в данном случае опускаю, ну а если есть желание, на эту тему есть отдельная статья

Но штука в том, что slug - это всего-навсего преобразованное к нижнему регистру название категории, в котором подчищены "левые" символы, а пробелы и подчеркивания преобразованы в дефисы. Поэтому не вижу смысла добавлять в форму поле пседовнима, и тем более, заставлять пользователей думать, как его заполнять. И всё же каким-то образом псевдониму надо присвоить значение - вот и проблема, которую помогут решить наблюдатели.

Для начала с помощью консоли создадим наблюдателя и сразу укажем модель, события которой надо прослушивать:

$ php artisan make:observer CategoryObserver --model=Category

 

Результатом является каталог project/app/Observers, в котором размещён файл CategoryObserver.php с нужным классом. Теперь мы хотим присвоить значение псевдониму до того, как модель будет сохранена или обновлена. Можно, конечно, было бы сразу тупо прописать методы creating() и updating(). Но мы же люди умные, а потому заглянем в доки, где сможем прочитать, что как в случае сохранения, так и в случае обновления будут инициированы события saving/saved. Нас интересует только первое. Редактируем код созданного файла:

<?php

namespace App\Observers;

use App\Category;

class CategoryObserver
{
    public function saving(Category $category)
    {
        $category->slug = str_slug($category->title);
    }
}

 

Напомню, str_slug() - это функция-хэлпер в Laravel, которая из строки делает пседовним. Причём не проблема, если строка будет на русском - кириллица будет преобразована в латиницу.

Осталось зарегистрировать наблюдателя. Для этого можно создать отдельного поставщика услуг, но сейчас обойдёмся без него - пропишем необходимый код в AppServiceProvider:

<?php

namespace App\Providers;

use App\Category;
use App\Observers\CategoryObserver;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Category::observe(CategoryObserver::class);
    }

    ...
}

 

Бум! Проблема решена.

Однако рано говорить стоп, зацепим ещё поле published. Предположим, что это чекбокс, а как мы знаем, если чекбокс не отмечен, то и в реквесте его не будет. Другими словами, если категория опубликована, а мы захотим снять её с публикации и во время редактирования формы уберём галочку с чекбокса - поле в базе данных не обновится. Есть разные методы борьбы с этим явлением, как, например, создание в форме скрытого поля и т.д. Но коль уж у нас есть наблюдатель, пусть поработает и тут. Вернёмся к методу saving(), и хотя "каждый такой метод получает модель в качестве единственного аргумента", нас это не испугает - достанем реквест и там:

<?php

namespace App\Observers;

use App\Category;

class CategoryObserver
{
    public function saving(Category $category)
    {
        $category->slug = str_slug($category->title);
        $category->live = request()->published ? true : false;
    }
}

 

Готово.

Да, пример простой, но всё же - минимум кода - и всё работает, как часы. Лаконично, легко читаемо и понятно, как и всегда в Laravel.