Статьи

Пылающий: модели Laravel на стероидах

Одна из (немногих) вещей, которые мне не нравятся в 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.

Я определенно рекомендую это. Вы? Вы пробовали это? Дайте нам знать в комментариях ниже!