Одна из (немногих) вещей, которые мне не нравятся в Laravel, заключается в том, что вы не можете легко перенести проверочный код с вашего контроллера на ваши модели. Когда я пишу программное обеспечение, мне нравится применять принцип «толстые модели, тощие контроллеры». Поэтому для меня написание кода проверки в контроллере не очень хорошая вещь.
Чтобы решить эту проблему, я хотел бы представить Ardent , отличный пакет для Laravel 4. Точнее, Ardent представляет себя как «Самопроверяющиеся интеллектуальные модели для Eloquent ORM в Laravel Framework 4». Другими словами: именно то, что нам нужно !
Как вы можете себе представить, это расширение класса Eloquent Model
. Этот пакет поставляется с некоторыми новыми функциями, утилитами и методами, предназначенными для проверки ввода и других мелочей.
Наше тестовое приложение
Чтобы лучше понять преимущества, которыми вы можете пользоваться при использовании Ardent, мы настроим небольшое тестовое приложение. Ничего сложного: простое приложение To-Do List.
Конечно, я не собираюсь реализовывать законченное приложение: я просто хочу объяснить некоторые принципы, поэтому я сделаю несколько контроллеров и моделей — без представлений. После этого я «переведу» код, используя Ardent.
Наш список дел будет включать в себя два разных объекта:
-
пользователь
- Я бы
- Имя
- фамилия
- Эл. адрес
- пароль
-
задача
- Я бы
- имя
- статус (выполнено / не выполнено)
Действительно простой проект. Однако, если вы не хотите писать код, не беспокойтесь: я уже подготовил миграцию, которую вы можете использовать для создания базы данных. Используй это!
Создайте файл миграции с помощью команды
php artisan migrate:make todo_setup
и затем заполните файл этим кодом:
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class TodoSetup extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function(Blueprint $table) { $table->increments('id')->unsigned(); $table->string('first_name'); $table->string('last_name'); $table->string('email'); $table->string('password', 60); $table->timestamps(); }); Schema::create('tasks', function(Blueprint $table) { $table->increments('id'); $table->string('name'); $table->boolean('status'); $table->integer('user_id')->unsigned(); $table->timestamps(); $table->index('user_id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); Schema::dropIfExists('tasks'); } }
Теперь у нас есть наши столы. Пришло время создавать наши модели. Даже здесь у нас очень мало строк для написания. Вот модель User
(это также модель по умолчанию).
<?php use Illuminate\Auth\UserTrait; use Illuminate\Auth\UserInterface; use Illuminate\Auth\Reminders\RemindableTrait; use Illuminate\Auth\Reminders\RemindableInterface; class User extends Eloquent implements UserInterface, RemindableInterface { use UserTrait, RemindableTrait; /** * The database table used by the model. * * @var string */ protected $table = 'users'; /** * The attributes excluded from the model's JSON form. * * @var array */ protected $hidden = array('password', 'remember_token'); public function tasks() { return $this->hasMany('Task'); } }
Я просто добавил метод tasks
чтобы описать связь с моделью Task
.
<?php class Task extends \Eloquent { protected $fillable = []; public function user() { return $this->belongsTo('User'); } }
Мы только сделали нашу отправную точку. Отныне, сразу после установки, мы увидим две разные ситуации: во-первых, «нормальную» версию кода без Ardent. Сразу после этого мы сделаем сравнение с «улучшенной» версией. Вы заметите разницу, поверьте мне.
Давайте начнем!
Установка Ardent
Установить Ardent очень легко с Composer. Просто добавьте зависимость в файл composer.json
вашего проекта.
{ "require": { "laravelbook/ardent": "2.*" } }
Затем, после обновления, вам просто нужно расширить класс Ardent
в ваших моделях следующим образом:
<?php class User extends \LaravelBook\Ardent\Ardent { // model code here! }
… и вы готовы к работе!
Проверка данных
Первое, что нужно сделать, — это проанализировать, как Ardent облегчает нашу жизнь в реализации валидации Мы уже знаем, как это сделать с Laravel. Давайте сделаем классический пример: метод POST, который будет обрабатывать данные, поступающие из формы.
В «нормальной» ситуации мы бы сделали что-то вроде этого:
<?php public function postSignup() { $rules = array( 'first_name' => 'required', 'last_name' => 'required', 'email' => 'required|email|unique:users', 'password' => 'required|min:8' ); $messages = array( 'first_name.required' => 'First name is required.', 'last_name.required' => 'Last name is required.', 'email.required' => 'Email is required.', 'password.required' => 'Password is required.', 'email.email' => 'Use a real email address!', 'email.unique' => 'This email address already exists!', 'password.min' => 'Password must be at least 8 character long.' ); $validator = Validator::make(Input::all(), $rules, $messages); if($validator->fails()) { return Redirect::to('user/signup')->with('errors', $validator->messages()); } $user = new User; $user->first_name = Input::get('first_name'); $user->last_name = Input::get('last_name'); $user->email = Input::get('email'); $user->password = Hash::make(Input::get('password')); if($user->save()) { $status = 1; } else { $status = 0; } return Redirect::to('user/signup')->with('status', $status); }
… как насчет Ardent?
С Ardent все немного меняется. Прежде всего, как вы можете легко представить, правила валидации будут перенесены непосредственно в модель: именно здесь мы начнем «рестайлинг». Итак, откройте файл модели и измените его следующим образом:
<?php use Illuminate\Auth\UserTrait; use Illuminate\Auth\UserInterface; use Illuminate\Auth\Reminders\RemindableTrait; use Illuminate\Auth\Reminders\RemindableInterface; class User extends \LaravelBook\Ardent\Ardent implements UserInterface, RemindableInterface { public static $rules = array( 'first_name' => 'required', 'last_name' => 'required', 'email' => 'required|email|unique:users', 'password' => 'required|min:8' ); public static $customMessages = array( 'first_name.required' => 'First name is required.', 'last_name.required' => 'Last name is required.', 'email.required' => 'Email is required.', 'password.required' => 'Password is required.', 'email.email' => 'Use a real email address!', 'email.unique' => 'This email address already exists!', 'password.min' => 'Password must be at least 8 character long.' ); use UserTrait, RemindableTrait; /** * The database table used by the model. * * @var string */ protected $table = 'users'; /** * The attributes excluded from the model's JSON form. * * @var array */ protected $hidden = array('password', 'remember_token'); public function tasks() { return $this->hasMany('Task'); } }
Что произошло? Немного: мы изменили базовый класс нашей модели (с Eloquent
на Ardent
) и перенесли (используя тот же синтаксис, просто копию) правила проверки здесь, с пользовательскими сообщениями об ошибках.
Итак, теперь возникает вопрос: что мы будем писать в нашем контроллере?
Давайте проверим это:
<?php class UserController extends \BaseController { public function postSignup() { $user = new User; $user->first_name = Input::get('first_name'); $user->last_name = Input::get('last_name'); $user->email = Input::get('email'); $user->password = Hash::make(Input::get('password')); if(!$user->save()) { return Redirect::to('user/signup')->with('errors', $user->errors()->all()); } return Redirect::to('user/signup')->with('status', 1); } }
Больше никаких инструкций по валидации здесь. Все они исчезли. Однако это не просто «перемещение»: метод $user->save()
теперь будет возвращать false
если на этапе проверки есть некоторые проблемы. Затем вы сможете получать ошибки с помощью метода $user->errors()->all()
. Никаких странных классов: возвращаемый объект будет классическим MessageBag
которым вы, возможно, уже встречались во время работы с Laravel.
Однако, если вы предпочитаете, вы также можете использовать свойство $user->validationErrors
. В этом случае или если вы хотите получить ошибки, связанные с полем, просто используйте $user->validationErrors->get('field_name')
.
Теперь очень вероятно, что вы думаете: «Хорошо, но каково реальное преимущество, помимо меньшего количества строк кода?»
Давайте начнем с самого важного: лучшая организация кода означает лучшую поддержку проекта. В простых приложениях вы не можете считать это приоритетом, но когда дело доходит до больших проектов, все может быть легко перепутано с одним неправильным решением. Позвольте мне привести пример из реальной ситуации. Мы разработали наше замечательное приложение To-Do List, и оно очень быстро растет. Нам определенно нужен RESTful API для мобильного приложения. Использование контроллера «обычным» способом будет означать написание еще одной процедуры регистрации для API. Два разных блока с одинаковым кодом! Это не хорошо. Где старый добрый принцип СУХОГО (не повторяй себя)?
Таким образом, наилучшей практикой будет написание метода signup()
в модели, которая обрабатывает все. Использование Ardent означает обработку действительно всего : от проверки до процедуры сохранения. Без этого мы не смогли бы завершить первый этап.
Второе преимущество является более практичным: модель с автогидратом. Давайте узнаем это вместе.
Модель Авто-Гидрат
Наш postSignup()
насчитывает ровно тринадцать строк кода. Даже если это кажется трудным, Ardent может уменьшить это число дальше. Посмотрите на этот пример:
<?php class UserController extends \BaseController { public function postSignup() { $user = new User; if(!$user->save()) { return Redirect::to('user/signup')->with('errors', $user->errors()->all()); } return Redirect::to('user/signup')->with('status', 1); } }
Здесь нет ошибок. Вы, наверное, уже поняли, что случилось.
Ardent имеет функцию автогидратации. Это означает, что когда вы создаете объект и вызываете метод $user->save()
, каждое поле автоматически заполняется данными объекта ввода. Конечно, вы должны будете дать правильное имя в каждом поле формы соответственно.
Итак, этот код:
$user = new User; $user->first_name = Input::get('first_name'); $user->last_name = Input::get('last_name'); $user->email = Input::get('email'); $user->password = Hash::make(Input::get('password')); $user->save();
будет иметь тот же эффект, что и
$user = new User; $user->save();
Из тридцати строк мы просто опустились до семи для всей процедуры регистрации.
Чтобы использовать эту функцию, вам нужно будет активировать ее. Просто переключите $autoHydrateEntityFromInput
в вашей модели на true, например, так:
<?php ... class User extends \LaravelBook\Ardent\Ardent { ... public $autoHydrateEntityFromInput = true; ... }
Выполнено!
Вы также часто будете иметь некоторые избыточные данные, которые вам не понадобятся для бизнес-логики. Подумайте о полях _confirmation
или _confirmation
CSRF. Ну, мы можем отбросить их с $autoPurgeRedundantAttributes
свойства $autoPurgeRedundantAttributes
модели. Как и раньше, просто переключите его на true
.
<?php ... class User extends \LaravelBook\Ardent\Ardent { ... public $autoHydrateEntityFromInput = true; public $autoPurgeRedundantAttributes = true; ... }
Теперь процедура чище, чем раньше.
Модельные Крючки
Еще одна хорошая особенность, о которой стоит упомянуть — это введение модельных крючков По сути, это список методов, которые, если они реализованы, вызываются в определенные моменты выполнения. Итак, для примера, метод afterUpdate()
будет вызываться перед каждым вызовом update()
. Метод beforeValidate()
будет вызываться перед каждой проверкой и так далее.
Вот список всех этих методов:
-
beforeCreate()
-
afterCreate()
-
beforeSave()
-
afterSave()
-
beforeUpdate()
-
afterUpdate()
-
beforeDelete()
-
afterDelete()
-
beforeValidate()
-
afterValidate()
Классическим примером может быть некоторая обработка данных перед процедурой сохранения. Как это:
<?php public function beforeSave() { $this->slug = Str::slug($this->title); return true; }
Каждый метод «before» имеет логическое возвращаемое значение. Если true, следующая операция выполняется нормально. Если метод возвращает false, операция будет остановлена. В приведенном выше методе мы создали слаг (и заполнил соответствующее поле) beforeSave()
сразу после проверки.
Существует также специальный совет о beforeSave()
и afterSave()
: вы можете объявить их во время выполнения. Посмотрите:
$user->save(array(), array(), array(), function ($model) { // beforeSave() code here... return true; }, function ($model) { // afterSave() code here... } );
Давайте подумаем о нескольких возможных применениях этих методов в нашем приложении.
Может быть, beforeSave()
для хеширования пароля?
<?php public function beforeSave() { $this->password = Hash::make($this->password); }
Или чистящий крючок прямо перед процедурой удаления пользователя?
<?php public function beforeDelete() { $this->tasks()->delete(); }
Есть много возможностей.
Определение отношений (горячий путь)
С Ardent вы также можете определять отношения более коротким способом, чем раньше. Давайте посмотрим, как мы на самом деле определяем наши отношения между моделями: в следующем примере показан метод tasks()
в модели User
.
<?php public function tasks() { return $this->hasMany('Task'); }
Использование Ardent для определения отношений означает определение простого массива, называемого $relationsData
.
<?php class User extends \LaravelBook\Ardent\Ardent { ... public static $relationsData = array( 'tasks' => array(self::HAS_MANY, 'Task') ); ... }
Это имеет точно такой же эффект. Ardent использует одно и то же соглашение об именах для связывания имен и методов без необходимости писать их по одному.
Однако есть много настроек, которые вы можете сделать:
<?php class User extends \LaravelBook\Ardent\Ardent { public static $relationsData = array( 'books' => array(self::HAS_MANY, 'Book', 'table' => 'users_have_books') ); }
Каждый элемент в $relationsData
имеет ключ (да, имя метода отношения) и массив с некоторыми параметрами.
- первый параметр (без ключа, это только первый) описывает тип отношения (hasOne, hasMany, serveTo, ownToMany, morphTo, morphOne, morphMany).
- второй параметр (без ключа, это всего лишь второй) определяет модель назначения текущего отношения;
- другие параметры имеют не определенную позицию, а ключ. Они могут быть:
- foreignKey: необязательный, используется для hasOne, hasMany, assignTo и serveToMany;
- table, otherKey, timestamps и pivotKeys: необязательно, используется для assignToMany;
- имя, тип и идентификатор: используется с morphTo, morphOne и morphMany;
Вывод
С большим набором преимуществ (также 126k загрузок и частых обновлений) очень трудно найти причину не использовать Ardent в вашем следующем приложении. Он подходит для любого типа проекта и, являясь расширением модели Eloquent, обеспечивает полную совместимость с проектами Laravel.
Я определенно рекомендую это. Вы? Вы пробовали это? Дайте нам знать в комментариях ниже!