20 хитростей в Laravel Eloquent о которых вы не знали

Eloquent ORM кажется простым механизмом, но под капотом существует много хитрых функций и способов достижения различных целей. В этой статье я покажу вам несколько трюков.

1. Инкременты и Декременты

Вместо этого:

$article = Article::find($article_id);
$article->read_count++;
$article->save();

Вы можете делать так:

$article = Article::find($article_id);
$article->increment('read_count');

И даже вот так:

Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1

2. XorY методы

В Eloquent существует несколько методов которые можно объеденять в один, что-то вроде "Сделай Х, и в случае неудачи сделай Y".

Пример 1: findOrFail():

Вместо этого:

$user = User::find($id); if (!$user) { abort (404); }

Вы можете делать так:

$user = User::findOrFail($id);

Пример 2: findOrCreate():

Вместо вот такого:

$user = User::where('email', $email)->first();
if (!$user) {
  User::create([
    'email' => $email
  ]);
}

Лучше делать вот так:

$user = User::firstOrCreate(['email' => $email]);

3. Метод boot()

В методе boot() вы можете переопределить поведение Eloquent модели:

class User extends Model
{
  public static function boot()
  {
    parent::boot();
    static::updating(function($model)
    {
      // do some logging
      // override some property like $model->something = transform($something);
    });
  }
}

Самым распростронненным примером использования метода boot() - установка значения по-умолчанию для какого-либо поля. Например, сгенерируем UUID в момент создания модели:

public static function boot()
{
  parent::boot();
  self::creating(function ($model) {
    $model->uuid = (string)Uuid::generate();
  });
}

4. Relationship с условиями и сортировкой

Вот так обычно определяют отношения:

public function users() {
  return $this->hasMany('App\User');  
}

Но знали ли вы, что уже на данном этапе можно добавить orderBy? Например, если мы хотим добавить relation только для определенного типа пользователей, с сортировкой по email, то мы можем сделать так:

public function approvedUsers() {
  return $this->hasMany('App\User')->where('approved', 1)->orderBy('email');
}

5. Свойства модели: timestmap, appends, и т.д.

В Eloquent модели имеется несколько свойств, о которых многие не знают. Самые популярные:

class User extends Model {
  protected $table = 'users';
  protected $fillable = ['email', 'password']; // which fields can be filled with User::create()
  protected $dates = ['created_at', 'deleted_at']; // which fields will be Carbon-ized
  protected $appends = ['field1', 'field2']; // additional values returned in JSON
}

Но существуют и другие, такие как:

protected $primaryKey = 'uuid'; // Если у вас нету поля 'id'
public $incrementing = false; // Указывает что primary key не имеет свойства auto increment
protected $perPage = 25; // А тут мы переопределяем кол-во результатов на странице при пагинации (по-умолчанию: 15)
const CREATED_AT = 'created_at'; const UPDATED_AT = 'updated_at'; // А этими константами можно переопределить название полей для created_at и updated_at
public $timestamps = false; // или можно указать что мы вообще не используем поля created_at и updated_at

На самом деле существует намного больше подобных свойств, я перечислил лишь несколько. Чтобы узнать про другие свойства откройте код и просмотрите все трейты которые используются.

6. Поиск нескольких записей

Все знают про метод find(), верно?

$user = User::find(1);

Но я был удивлен, что очень немногие знают что этот метод принимает так же массив с несколькими ключами:

$users = User::find([1,2,3]);

7. WhereX

В Eloquent существует элегантный способ превратить это:

$users = User::where('approved', 1)->get();

В это:

$users = User::whereApproved(1)->get();

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

Так же существует несколько подобных методов для работы с датами, которые Eloquent содержит изначально:

User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));

8. Сортировка по relation

Более сложный трюк. Что если у нас есть форумные темы и мы хоти отсортировать их по дате последнего поста? Довольно таки обыденная задача, верно?

Для начала мы пропишем отдельную связь latest post в модели темы:

public function latestPost()
{
  return $this->hasOne(\App\Post::class)->latest();
}

И затем, в нашем контроллере, мы можем творить магию:

$users = Topic::with('latestPost')->get()->sortByDesc('latestPost.created_at');

9. Eloquent::when() - избавляется от if-else

Многие из нас пишут подобные условные конструкции:

if (request('filter_by') == 'likes') {
  $query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
  $query->orderBy('created_at', request('ordering_rule', 'desc'));
}

Но есть более изящный способ это сделать:

$query = Author::query();
$query->when(request('filter_by') == 'likes', function ($q) {
  return $q->where('likes', '>', request('likes_amount', 0));
});
$query->when(request('filter_by') == 'date', function ($q) {
  return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});

10. BelongsTo и Модель по-умолчанию

Представим что у нас есть модель Post, которая belongsTo к модели Author, и мы выводим автора в шаблоне:

{{ $post->author->name }}

Но что случится если модель автора удалена или null по какой-нибудь причине? Будет ошибка! Конечно мы может сделать проверку:

//

Но тогда нам придется делать такие проверки в каждом месте где мы хотим получить имя автора. Есть более изящный способ сделать это с помощью Eloquent:

public function author()
{
  return $this->belongsTo('App\Author')->withDefault();
}

В данном примере relation author() вернет пустую модель Author, если в Post нету связи с реальной моделью. Мы можем даже указать свойства по-умолчанию для этой пустой модели:

public function author()
{
  return $this->belongsTo('App\Author')->withDefault([
    'name' => 'Guest Author'
  ]);
}

11. Order by Mutator

Представим что у нас есть такой код:

function getFullNameAttribute()
{
  return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}

Вы хотите сделать сортировку по full_name? Такой подход не сработает:

$clients = Client::orderBy('full_name')->get(); // так не работает

Решение довольно простое. Мы должны сортировать результаты ПОСЛЕ того как мы их получили:

$clients = Client::get()->sortBy('full_name'); // а вот так работает!

Обратите внимание что мы используем не orderBy, а sortBy, функцию из Collection.

12. Сортировка по-умолчанию для глобального scope

Что если мы хотим, чтобы запрос User::all() всегда был отсортирован по полю name? Мы можем назначить глобальный scope для определения такого поведения. Давайте вернемся к методу boot(), о котором мы говорили ранее, и используем его:

protected static function boot()
{
  parent::boot();

  // Order by name ASC
  static::addGlobalScope('order', function (Builder $builder) {
    $builder->orderBy('name', 'asc');
  });
}

Подробнее можно почитать в документации.

13. Raw запросы

Иногда нам нужно осуществлять "сырые" (raw) запросы к базе данных. К счастью Eloquent поддерживает и их:

// whereRaw
$orders = DB::table('orders')
  ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
  ->get();

// havingRaw
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();

// orderByRaw
User::where('created_at', '>', '2016-01-01')
->orderByRaw('(updated_at - created_at) desc')
->get();

14. Replicate: создать копию записи

Очень коротко: вот так можно создать копию записи в базе данных:

$task = Tasks::find(1);
$newTask = $task->replicate();
$newTask->save();

15. Метод Chunk для больших таблиц

Это больше относится к коллекциям, а не к Eloquent, но все равно. Если у вас есть большая таблица (с тысячами записей) и вы не можете получить их все за один запрос, то можно использовать метод chunk, который будет доставать записи по "чуть-чуть":

User::chunk(100, function ($users) {
  foreach ($users as $user) {
    // ...
  }
});

16. Создаем дополнительные вещи при создании модели

Все мы знаем про команду php artisan make:model Company. Но знали ли вы, что вы сразу можете сгенерировать: миграцию, контроллер, и даже указать что контроллер должен быть REST? Используя дополнительные флаги мы можем сделать это одной командой:

php artisan make:model Company -mcr

Где флаги означают:

  • m - создать файл миграции
  • c - создать контроллер
  • r - контроллер должен быть REST

17. Не обновлять update_at при сохранении

Зналил ли вы, что метод ->save() принимает дополнительные параметры? Например, мы можем указать что не нужно обновлять timestamps, если нам это не нужно:

$product = Product::find($id);
$product->updated_at = '2019-01-01 10:00:00';
$product->save(['timestamps' => false]);

Такой код не будет обновлять update_at при вызове save().

18. Узнать результат update()

Задавались ли вы когда-нибудь вопросом что возвращает такой код:

$result = $products->whereNull('category_id')->update(['category_id' => 2]);

Я имею ввиду не что конкретно делает метод update(), а что в результате будет в переменной $result?

Ответ: кол-во затронутых строк! Так что если вы захотите узнать сколько строк обновил ваш вызов update() - вам не требуется ничего делать! Вы можете просто посмотреть на значение которое он вернул.

19. Преобразование скобочек из SQL в Eloquent запрос

Что если у вас имеется подобный SQL запрос:

... WHERE (gender = 'Male' and age >= 18) or (gender = 'Female' and age >= 65)

Как правильно составить Eloquent запрос для него? Вот так неправильно:

$q->where('gender', 'Male'); $q->orWhere('age', '>=', 18);
$q->where('gender', 'Female');
$q->orWhere('age', '>=', 65);

У вас получится неправильный SQL запрос. Правильный способ чуть более сложный, и заключается в использовании замыканий:

$q->where(function ($query) {
  $query->where('gender', 'Male')
    ->where('age', '>=', 18);
})->orWhere(function($query) {
  $query->where('gender', 'Female')
    ->where('age', '>=', 65);
})

20. orWhere с несколькими параметрами

В завершение: вы можете передавать массив параметров в orWhere() метод. Обычно делают так:

$q->where('a', 1);
$q->orWhere('b', 2);
$q->orWhere('c', 3);

Вы же можете быть более крутыми, и делать вот так:

$q->where('a', 1);
$q->orWhere(['b' => 2, 'c' => 3]);

Я уверен что существует еще множется трюков, о которых мы не знаем. Возможно кто-то поделится ими в комментариях.

Перевод статьи 20 Laravel Eloquent Tips and Tricks.

Опубликовано:

Категории: Статьи