20 хитростей при работе с Laravel Eloquent

20 хитростей Laravel Eloquent

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

 

01: Увеличение и уменьшение

Вместо:

$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

 

02: Методы XorY

Eloquent имеет довольно много функций, которые объединяют оба метода, типа «сделайте X, если не получится, то сделайте Y».

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

Вместо:

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

Сделайте:

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

Пример 2 — firstOrCreate():

Вместо:

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

Сделайте:

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

 

03: Модельный метод boot()

В модели Eloquent есть волшебный метод boot(), в котором вы можете переопределить дефолтное поведение:

class User extends Model
{
    public static function boot()
    {
        parent::boot();
        static::updating(function($model)
        {
            // делаем протоколирование
            // переопределяем некоторые свойства, например $model->something = transform($something);
        });
    }
}

Один из самых популярных примеров, это установка значений для некоторых полей в момент создания объекта модели.
Например, генерируем поле UUID в этот момент.

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

 

04: Отношения с условиями и сортировкой

Это стандартный способ задания отношений:

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

А вы знали, что мы сразу можем добавить методы where и orderBy?

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

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

 

05: Свойства модели: временные метки, добавления и тому подобное

Есть несколько «параметров» модели Eloquent в форме свойств этого класса.
Самые популярные из них следующие:

class User extends Model {
    protected $table = 'users';
    protected $fillable = ['email', 'password']; // поля, которые можно заполнять при User::create()
    protected $dates = ['created_at', 'deleted_at']; // поля, которые должны быть преобразованы к датам
    protected $appends = ['field1', 'field2']; // дополнительные значения, возвращаемые в JSON
}

Но подождите, это еще не все:

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

 

И есть еще больше, я просто перечислил самые интересные. Чтобы узнать больше, посмотрите код абстрактного класса Model

06: Найти несколько значений

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

$user = User::find(1);

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

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

 

07: WhereX

Есть элегантный способ превратить это:

$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'));

 

08: Сортировка по отношениям

Немного более сложная хитрость. Например, у вас есть темы на форуме и вы хотите отсортировать их по последнему сообщению. Обычное дело для форумов, так ведь?

Сначала сделайте отдельное отношение для последнего сообщения в теме:

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

А теперь, в нашем контроллере, мы займемся магией:

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

 

09: Eloquent::when() – без if-else

Многие из нас пишут условные запросы с помощью 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'));
}

Но есть способ лучше — используйте when():

$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'));
});

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

$query = User::query();
$query->when(request('role', false), function ($q, $role) {
    return $q->where('role_id', $role);
});
$authors = $query->get();

 

10: BelongsTo дефолтная модель

Допустим у вас есть Сообщение (Post), принадлежащее Автору (Author) и такой вот код в шаблоне Blade:

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

Но, что если автор удалён или не установлен по какой-то другой причине? Тогда вы получите ошибку, что-то вроде «property of non-object»
Конечно, вы можете предотвратить это:

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

Но вы можете сделать это на уровне Eloquent отношений:

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

В этом примере отношение author() вернет пустую модель App\Author, если за Сообщением не закреплен ни один Автор.

Кроме того, мы можем присвоить дефолтное значения этой дефолтной модели.

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

 

11: Сортировка по Преобразователю

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

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'); // заработало!

 

12: Дефолтная сортировка в глобальном Scope

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

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

    // Сортировка поля name по возрастанию (ASC)
    static::addGlobalScope('order', function (Builder $builder) {
        $builder->orderBy('name', 'asc');
    });
}

Узнайте больше о глобальных Scope здесь.

 

13: Методы сырых запросов

Иногда нам нужно добавить сырые запросы к нашим вызовам 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, скорее о Collection, тем не менее, вот мощный способ обработки больших наборов данных: вы можете разбить их на части.

Вместо:

$users = User::all();
foreach ($users as $user) {
// ...

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

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

 

16: Дополнительные действия при создании модели

Мы все знаем эту команду Artisan

php artisan make:model Company

Но знаете ли вы, что есть три полезных флага для генерации файлов, связанных с моделью?

php artisan make:model Company -mcr

-m создаст файл миграции
-c создаст контроллер
-r указание, что контроллер должен содержать готовые ресурсы

 

17: Переопределение updated_at при сохранении

А вы знали, что метод ->save() может принимать параметры?
В результате мы можем заставить его «игнорировать» дефолтную функцию заполнения поля updated_at текущей отметкой времени. Смотрите:

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

Здесь мы переопределяем дефолтную updated_at на нашу собственную.

 

18: Каков результат update()?

Задумывались ли вы о том, что на самом деле возвращает этот код?

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

Я имею в виду, что обновление выполняется в базе данных, но в итоге чему будет равна переменная $result?

Ответ количество затронутых строк. Поэтому, если вам нужно проверить, сколько строк было затронуто, вам не нужно больше ничего вызывать — метод update() вернет вам это число.

 

19: Преобразование скобок в запросе Eloquent

Что делать, если у вас в SQL запросе смешаны AND и OR, например:

... 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);

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

$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]);

 

Автор: Povilas Korop
Перевод: Demiurge Ash