Версия для печати | оригинал записи

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

ash — Log-ово Хозяина

01 апреля 2019 19:33
20 хитростей при работе с Laravel Eloquent
[attachid=599011]

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

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

Вместо:

Цитирую (PHP)
$article = Article::find($article_id);
$article
->read_count++;
$article
->save();

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

Цитирую (PHP)
$article = Article::find($article_id);
$article
->increment('read_count');

И так тоже:

Цитирую (PHP)
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():

Вместо:

Цитирую (PHP)
$user = User::find($id);
if (!$user) { abort (404); }

Сделайте:

Цитирую (PHP)
$user = User::findOrFail($id);

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

Вместо:

Цитирую (PHP)
$user = User::where('email', $email)->first();
if (!$user) {
  User::create([
    'email' => $email
 
]);
}

Сделайте:

Цитирую (PHP)
$user = User::firstOrCreate(['email' => $email]);


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

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

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

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

Цитирую (PHP)
public static function boot()
{
  parent
::boot();
  self::creating(function ($model) {
    $model
->uuid = (string)Uuid::generate();
  });
}


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

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

Цитирую (PHP)
public function users() {
    return $this->hasMany('App\User');   
}

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

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

Цитирую (PHP)
public function approvedUsers() {
    return $this->hasMany('App\User')->where('approved', 1)->orderBy('email');
}


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

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

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

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

Цитирую (PHP)
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(), верно?

Цитирую (PHP)
$user = User::find(1);

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

Цитирую (PHP)
$users = User::find([1,2,3]);


07: WhereX

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

Цитирую (PHP)
$users = User::where('approved', 1)->get();

в это:

Цитирую (PHP)
$users = User::whereApproved(1)->get();

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

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

Цитирую (PHP)
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: Сортировка по отношениям

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

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

Цитирую (PHP)
public function latestPost()
{
    return $this->hasOne(\App\Post::class)->latest();
}

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

Цитирую (PHP)
$users = Topic::with('latestPost')->get()->sortByDesc('latestPost.created_at');


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

Многие из нас пишут условные запросы с помощью if-else, что-то вроде такого:

Цитирую (PHP)
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():

Цитирую (PHP)
$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'));
});

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

Цитирую (PHP)
$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:

Цитирую (PHP)
{{ $post->author->name }}

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

Цитирую (PHP)
{{ $post->author->name ?? '' }}

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

Цитирую (PHP)
public function author()
{
    return $this->belongsTo('App\Author')->withDefault();
}

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

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

Цитирую (PHP)
public function author()
{
    return $this->belongsTo('App\Author')->withDefault([
        'name' => 'Guest Author'
    ]);
}


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

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

Цитирую (PHP)
function getFullNameAttribute()
{
  return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}

Теперь попробуйте отсортировать по full_name. Не сработает:

Цитирую (PHP)
$clients = Client::orderBy('full_name')->get(); // не работает

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

Цитирую (PHP)
$clients = Client::get()->sortBy('full_name'); // заработало!


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

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

Цитирую (PHP)
protected static function boot()
{
    parent
::boot();

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

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


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

Иногда нам нужно добавить сырые запросы к нашим вызовам Eloquent. К счастью, для этого есть функционал.

Цитирую (PHP)
// 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: копия строки

Без углубленных объяснений — вот лучший способ сделать копию записи базы данных:

Цитирую (PHP)
$task = Tasks::find(1);
$newTask
= $task->replicate();
$newTask
->save();


15: Метод chunk() для больших таблиц

Не совсем об Eloquent, скорее о Collection, тем не менее, вот мощный способ обработки больших наборов данных: вы можете разбить их на части.

Вместо:

Цитирую (PHP)
$users = User::all();
foreach ($users as $user) {
    // ...

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

Цитирую (PHP)
User::chunk(100, function ($users) {
    foreach ($users as $user) {
        // ...
    }
});


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

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

Цитирую (PHP)
php artisan make:model Company

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

Цитирую (PHP)
php artisan make:model Company -mcr

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


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

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

Цитирую (PHP)
$product = Product::find($id);
$product
->updated_at = '2019-01-01 10:00:00';
$product
->save(['timestamps' => false]);

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


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

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

Цитирую (PHP)
$result = $products->whereNull('category_id')->update(['category_id' => 2]);

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

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


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

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

Цитирую (SQL)
... WHERE (gender = 'Male' and age >= 18) or (gender = 'Female' and age >= 65)

Как перевести это в Eloquent? Неправильный способ:

Цитирую (PHP)
$q->where('gender', 'Male');
$q
->orWhere('age', '>=', 18);
$q
->where('gender', 'Female');
$q
->orWhere('age', '>=', 65);

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

Цитирую (PHP)
$q->where(function ($query) {
    $query
->where('gender', 'Male')
        ->where('age', '>=', 18);
})->orWhere(function($query) {
    $query
->where('gender', 'Female')
        ->where('age', '>=', 65);
})


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

Вы можете передать массив параметров в orWhere().
«Обычный» способ:

Цитирую (PHP)
$q->where('a', 1);
$q
->orWhere('b', 2);
$q
->orWhere('c', 3);

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

Цитирую (PHP)
$q->where('a', 1);
$q
->orWhere(['b' => 2, 'c' => 3]);


Автор: PovilasKorop
Перевод: Demiurge Ash[/CUT]

Комменты: 0


Demiart © 2019