Eloquent и модели в Laravel 5.* для работы с DB

Создадать модель Eloquent

# php artisan make:model User

По умолчанию модель будет создана в директории app.

Для создании модели и миграции команды:

#php artisan make:model User --migration

#php artisan make:model User -m

Пример модели Flight, которая используется для работы с базой данных flights:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    //
}

В данном примере не указана таблица явным образом, то в соответствии с принятым соглашением будет использовано имя класса в нижнем регистре (snake case) и во множественном числе. В данном случае Eloquent предположит, что модель Flight хранит свои данные в таблице flights.

Для указания таблицы используется переменная $table:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $table = 'my_flights';
}

Для указания ключа таблицы используется переменная $primaryKey:

...
	protected $primaryKey = 'id';
...

По умолчанию считается, что ключ является инкрементным числом (int — AUTO_INCREMENT), для регулирования есть переменная $incrementing, при указании false, можно использовать неинкрементный или нечисловой первичный ключ:

...
	protected $incrementing = false;
...

По умолчанию Eloquent ожидает наличия в таблицах столбцов created_at и updated_at.

Если не нужно, чтобы они автоматически обрабатывались в Eloquent, нужно установить свойство $timestamps класса модели в false:

...
	public $timestamps = false;
...

Для изменения формата отметок времени, нужно задать свойство $dateFormat в модели. Это свойство определяет, как атрибуты времени будут храниться в базе данных, а также задаёт их формат при сериализации модели в массив или JSON:

...
	protected $dateFormat = 'U';
...

Для изменения имен столбцов для хранения отметок времени, можно задать константы CREATED_AT и UPDATED_AT:

...
	const CREATED_AT = 'creation_date';
	const UPDATED_AT = 'last_update';
...

Поменять модель соединения с DB:

...
	protected $connection = 'connection-name';
...

 

Получение моделей

Мотоды get и all возвращают результат в массиве (экземпляр: Illuminate\Database\Eloquent\Collection):

Получение из таблицы все записи через модель:

<?php

use App\Flight;

$flights = App\Flight::all();

foreach ($flights as $flight) {
    echo $flight->name;
}

Добавление дополнительных ограничений

$flights = App\Flight::where('active', 1)
               ->orderBy('name', 'desc')
               ->take(10)
               ->get();
Все методы, доступные в конструкторе запросов, также доступны при работе с моделями Eloquent.

Разделение результата на блоки при большом количестве возввращаемых данных

Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        //
    }
});

В данном примере в метод chunk передается по сколько строк получать и функция для обработки.

Использование курсоров

Метод cursor позволяет проходить по записям базы данных, используя курсор, который выполняет только один запрос. При обработке больших объёмов данных метод cursor может значительно уменьшить расходование памяти:

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    //
}

 

Получение одиночных моделей / агрегатных функций

Получить конкретные записи с помощью find или first. Вместо коллекции моделей эти методы возвращают один экземпляр модели:

// Получение модели по её первичному ключу...
$flight = App\Flight::find(1);

// Получение первой модели, удовлетворяющей условиям...
$flight = App\Flight::where('active', 1)->first();

Вызвать метод find с массивом первичных ключей, который вернёт коллекцию подходящих записей:

$flights = App\Flight::find([1, 2, 3]);

Исключения «Не найдено»

Иногда нужно получить исключение, если определённая модель не была найдена. Это удобно в роутах и контроллерах. Методы findOrFail и firstOrFail получают первый результат запроса. А если результатов не найдено, выбрасывается исключение Illuminate\Database\Eloquent\ModelNotFoundException:

$model = App\Flight::findOrFail(1);

$model = App\Flight::where('legs', '>', 100)->firstOrFail();

Если исключение не найдено, автоматически указывается заголовок HTTP 404:

Route::get('/api/flights/{id}', function ($id) {
    return App\Flight::findOrFail($id);
});

count, sum, max и другие агрегатные методы, предоставляемые конструктором запросов count, sum, max и другие агрегатные методы, предоставляемые конструктором запросов

$count = App\Flight::where('active', 1)->count();

$max = App\Flight::where('active', 1)->max('price');

 

Вставка и изменение моделей

Для создания новой записи в БД нужно создать экземпляр модели, задать атрибуты модели и вызовать метод save:

<?php

namespace App\Http\Controllers;

use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class FlightController extends Controller
{
    /**
     * Создание нового экземпляра рейса.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        // Проверка запроса...

        $flight = new Flight;

        $flight->name = $request->name;

        $flight->save();
    }
}

В этом примере присвоено значение параметра name из входящего HTTP-запроса атрибуту name экземпляра модели App\Flight. При вызове метода save запись будет вставлена в таблицу. Отметки времени created_at и updated_at будут автоматически установлены при вызове save, поэтому их не нужно задавать вручную.

 

Метод save можно использовать и для изменения существующей модели в БД. Для изменения модели нужно получить её, изменить необходимые атрибуты и вызвать метод save. Отметка времени updated_at будет установлена автоматически:

$flight = App\Flight::find(1);

$flight->name = 'New Flight Name';

$flight->save();

Массовые изменения

App\Flight::where('active', 1)
          ->where('destination', 'San Diego')
          ->update(['delayed' => 1]);

Метод update ожидает массив пар столбец/значение, обозначающий, какие столбцы необходимо изменить.

При использовании массовых изменений Eloquent для изменяемых моделей не будут выбрасываться события saved и updated. Это происходит потому, что на самом деле модели вообще не извлекаются при массовом изменении.

Массовое назначение

Можно использовать метод create для создания и сохранения модели одной строкой. Метод вернёт добавленную модель. Однако перед этим нужно определить либо свойство fillable, либо guarded в классе модели, так как все модели Eloquent изначально защищены от массового заполнения.

Для использования данного метода необходимо в модели указать какие поля можно сохранять:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * Атрибуты, для которых разрешено массовое назначение.
     *
     * @var array
     */
    protected $fillable = ['name'];
}

Использование метода create. Этот метод вернет ключ новой записи:

$flight = App\Flight::create(['name' => 'Flight 10']);

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

$flight->fill(['name' => 'Flight 22']);

Защитные атрибуты

В то время как параметр $fillable служит «белым списком» атрибутов, для которых разрешено массовое назначение. А параметр $guarded служит «чёрным списком». Параметр $guarded должен содержать массив атрибутов, для которых будет запрещено массовое назначение. Атрибутам, не вошедшим в этот массив, будет разрешено массовое назначение.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * Атрибуты, для которых запрещено массовое назначение.
     *
     * @var array
     */
    protected $guarded = ['price'];
}

Чтобы разрешить массовое назначение всем атрибутам, свойство $guarded определяется как пустой массив:

...
	protected $guarded = [];
...

Другие методы создания

firstOrCreate / firstOrNew

Есть ещё два метода, используемые для создания моделей с помощью массового заполнения: firstOrCreate и firstOrNew. Метод firstOrCreate пытается найти запись БД, используя указанные пары столбец/значение. Если модель не найдена в БД, запись будет вставлена в БД с указанными атрибутами.

// Получить рейс по атрибутам или создать, если он не существует...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);

// Получить рейс по атрибутам или создать его с именем и отложенными атрибутами...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'], ['delayed' => 1]
);

// Получить по имени или образцу...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

// Получить по имени или образцу, с именем и отложенными атрибутами...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'], ['delayed' => 1]
);

updateOrCreate

Можно столкнуться с ситуациями, когда надо обновить существующую модель или создать новую, если её пока нет. Laravel предоставляет метод updateOrCreate для выполнения этой задачи за один шаг. Подобно методу firstOrCreate, метод updateOrCreate сохраняет модель, поэтому не надо вызывать метод save():

// Если есть рейс из Oakland в San Diego, установить цену $99.
// Если подходящей модели не существует, создать новую.
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99]
);

 

Удаление моделей

Для удаления модели вызовите метод delete на её экземпляре:

$flight = App\Flight::find(1);

$flight->delete();

Удаление существующей модели по ключу

App\Flight::destroy(1);

App\Flight::destroy([1, 2, 3]);

App\Flight::destroy(1, 2, 3);

Удаление модели запросом

$deletedRows = App\Flight::where('active', 0)->delete();
При использовании массового удаления Eloquent для удаляемых моделей не будут выброшены события deleting и deleted. Это происходит потому, что на самом деле модели вообще не извлекаются при выполнении оператора удаления.

Псевдоудаление

Кроме обычного удаления записей из БД Eloquent также может «псевдоудалять» модели. Модель при таком удалении на самом деле остаётся в базе данных, но в БД устанавливается её атрибут deleted_at. Если у модели ненулевое значение deleted_at, значит модель псевдоудалена. Для включения псевдоудаления для модели используется для неё трейт Illuminate\Database\Eloquent\SoftDeletes и добавляется столбец deleted_at в свойство $dates:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{
    use SoftDeletes;

    /**
     * Атрибуты, которые должны быть преобразованы в даты.
     *
     * @var array
     */
    protected $dates = ['deleted_at'];
}

Необходимо добавить столбец deleted_at в таблицу:

Schema::table('flights', function ($table) {
    $table->softDeletes();
});

Для определения того, удалён ли экземпляр модели, используется метод trashed:

if ($flight->trashed()) {
    //
}

Запрос псевдоудалённых моделей

Включение псевдоудалённых моделей

$flights = App\Flight::withTrashed()
                ->where('account_id', 1)
                ->get();

Использован в истории:

$flight->history()->withTrashed()->get();

Получение только псевдоудалённых моделей

$flights = App\Flight::onlyTrashed()
                ->where('airline_id', 1)
                ->get();

Восстановление псевдоудалённых моделей

$flight->restore();

Быстрое восстановление нескольких моделей

App\Flight::withTrashed()
        ->where('airline_id', 1)
        ->restore();

Использован в истории:

$flight->history()->restore();

Перманентное удаление моделей

Используется метод forceDelete, чтобы перманентно удалить псевдоудалённую модель из БД:

// Принудительное удаление одного экземпляра модели...
$flight->forceDelete();

// Принудительное удаление всех связанных моделей...
$flight->history()->forceDelete();

 

Заготовки запросов (query scopes)

Глобальные заготовки

Глобальные заготовки позволяют добавить ограничения во все запросы для данной модели. Собственная функция Laravel псевдоудаление использует глобальные заготовки, чтобы получать из базы данных только «неудалённые» модели. Написание собственных глобальных заготовок обеспечивает удобный и простой способ наложить определённые ограничения на каждый запрос для конкретной модели.

Написание глобальных заготовок

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class AgeScope implements Scope
{
    /**
     * Применение заготовки к данному конструктору запросов Eloquent.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('age', '>', 200);
    }
}
Если ваша глобальная заготовка добавляет столбцы для выбора спецификации запроса, следует использовать метод addSelect вместо select. Это предотвратит непреднамеренную замену существующей спецификации выбора запроса.

Применение глобальных заготовок

Для назначения глобальной заготовки (скоупа) на модель надо переопределить метод boot данной модели и использовать метод addGlobalScope:

<?php

namespace App;

use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * "Загружающий" метод модели.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new AgeScope);
    }
}

После добавления заготовки запрос к User::all() будет создавать следующий SQL:

select * from `users` where `age` > 200

Анонимные глобальные заготовки

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class User extends Model
{
    /**
     * "Загружающий" метод модели.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope('age', function (Builder $builder) {
            $builder->where('age', '>', 200);
        });
    }
}

Удаление глобальных заготовок

User::withoutGlobalScope(AgeScope::class)->get();

удалить несколько или все глобальные заготовки

// Удалить все глобальные заготовки...
User::withoutGlobalScopes()->get();

// Удалить некоторые глобальные заготовки...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

Локальные заготовки

Заготовки (скоупы) позволяют повторно использовать логику запросов в моделях. Например, если часто требуется получать пользователей, которые сейчас «популярны». Для создания заготовки просто начните имя метода с префикса scope.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Scope a query to only include popular users.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopePopular($query)
    {
        return $query->where('votes', '>', 100);
    }

    /**
     * Scope a query to only include active users.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeActive($query)
    {
        return $query->where('active', 1);
    }
}

Использование локальной заготовки

$users = App\User::popular()->active()->orderBy('created_at')->get();

Динамические заготовки

Иногда может потребоваться определить заготовку, которая принимает параметры. Для этого просто добавляются эти параметры в заготовку. Они должны быть определены после параметра $query:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Scope a query to only include users of a given type.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param mixed $type
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeOfType($query, $type)
    {
        return $query->where('type', $type);
    }
}

А затем передают их при вызове метода заготовки:

$users = App\User::ofType('admin')->get();

 

События

Модели Eloquent инициируют несколько событий, что позволяет добавить к ним обработчики с помощью следующих методов: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored. События позволяют выполнять код при каждом сохранении или изменении класса конкретной модели в БД.

Когда новая модель сохраняется первый раз, возникают события creating и created. Если модель уже существовала на момент вызова метода save(), вызываются события updating / updated. В обоих случаях также возникнут события saving / saved.

Вначале нужно определить свойство $events в Eloquent-модели, которая сопоставляет различные моменты жизненного цикла модели Eloquent с классами событий:

<?php

namespace App;

use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The event map for the model.
     *
     * @var array
     */
    protected $events = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

Наблюдатели

Если прослушивается много событий для определённой модели, можно использовать наблюдателей (обсерверов) для объединения всех слушателей в единый класс. В классах наблюдателей названия методов отражают те события Eloquent, которые нужно прослушивать. Каждый такой метод получает модель в качестве единственного аргумента. В Laravel нет стандартного каталога для наблюдателей, поэтому можно создать любой каталог для хранения классов наблюдателей:

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * Listen to the User created event.
     *
     * @param  User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * Listen to the User deleting event.
     *
     * @param  User  $user
     * @return void
     */
    public function deleting(User $user)
    {
        //
    }
}

Для регистрации наблюдателя используйте метод observe на наблюдаемой модели. Можно зарегистрировать наблюдателей в методе boot одного из сервис-провайдеров. В этом примере зарегистрируется наблюдатель в AppServiceProvider:

<?php

namespace App\Providers;

use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Загрузка любых сервисов приложения.
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }

    /**
     * Регистрация сервис-провайдера.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}