
Наблюдатели моделей в Laravel
13.07.2018 19:35 | 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.