Может войдёшь?
Черновики Написать статью Профиль

Eloquent ORM

перевод документация 5.х

  1. 1. Введение
  2. 2. Определение моделей
    1. 2.1. Условия для моделей Eloquent
  3. 3. Получение моделей
    1. 3.1. Коллекции
    2. 3.2. Разделение результата на блоки
  4. 4. Получение одиночных моделей / агрегатные функции
    1. 4.1. Агрегатные функции
  5. 5. Вставка и изменение моделей
    1. 5.1. Вставки
    2. 5.2. Изменения
    3. 5.3. Массовое заполнение
    4. 5.4. Другие методы создания
  6. 6. Удаление моделей
    1. 6.1. Мягкое удаление
    2. 6.2. Запрос мягко удалённых моделей
  7. 7. Заготовки запросов
    1. 7.1. Глобальные заготовки
    2. 7.2. Локальные заготовки
  8. 8. Отношения
    1. 8.1. Один к одному
    2. 8.2. Один ко многим
    3. 8.3. Многие ко многим
    4. 8.4. Ко многим через
    5. 8.5. Полиморфические отношения
    6. 8.6. Полиморфические связи многие ко многим
    7. 8.7. Запросы к отношениям
    8. 8.8. Динамические свойства
    9. 8.9. Активная загрузка
    10. 8.10. Ограничения активной загрузки
    11. 8.11. Ленивая активная загрузка
    12. 8.12. Вставка связанных моделей
    13. 8.13. Связывание моделей (belongs to)
    14. 8.14. Вставка связанных моделей (многие ко многим)
    15. 8.15. Обновление времени владельца
    16. 8.16. Работа со связующими таблицами
  9. 9. Коллекции
  10. 10. Читатели и преобразователи
    1. 10.1. Преобразователи дат
    2. 10.2. Изменение атрибутов
  11. 11. События моделей
  12. 12. Наблюдатели моделей
  13. 13. Генерация URL модели
  14. 14. Преобразование в массивы и JSON
Этот перевод актуален для англоязычной документации на (ветка 5.3) , (ветка 5.2) , (ветка 5.1) и (ветка 5.0). Опечатка? Выдели и нажми Ctrl+Enter.

Введение

Система объектно-реляционного отображения (ORM) Eloquent — красивая и простая реализация шаблона ActiveRecord в Laravel для работы с базами данных. Каждая таблица имеет соответствующий класс-модель, который используется для работы с этой таблицей. Модели позволяют запрашивать данные из таблиц, а также вставлять в них новые записи.

Прежде чем начать, настройте ваше соединение с БД в файле config/database.php. Подробнее о настройке БД читайте в документации.

Определение моделей

Для начала создадим модель Eloquent. Модели обычно располагаются в папке app, но вы можете поместить их в любое место, в котором работает автозагрузчик в соответствии с вашим файлом composer.json. Все модели Eloquent наследуют класс Illuminate\Database\Eloquent\Model.

Простейший способ создать экземпляр модели — с помощью Artisan-команды shmake:model:

shphp artisan make:model User

Если вы хотите создать миграцию БД при создании модели, используйте параметр sh--migration или sh-m:

shphp artisan make:model User --migration

php artisan make:model User -m

Условия для моделей Eloquent

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

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Flight extends Model
{
  
//
}

Имена таблиц

Заметьте, что мы не указали, какую таблицу Eloquent должен привязать к нашей модели. Если это имя не указано явно, то в соответствии с принятым соглашением будет использовано имя класса в нижнем регистре и во множественном числе. В нашем случае Eloquent предположит, что модель PHPFlight хранит свои данные в таблице flights. Вы можете указать произвольную таблицу, определив свойство PHPtable в классе модели:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Flight extends Model
{
  
/**
   * Связанная с моделью таблица.
   *
   * @var string
   */
  
protected $table 'my_flights';
}

Первичные ключи

Eloquent также предполагает, что каждая таблица имеет первичный ключ с именем PHPid. Вы можете определить свойство PHP$primaryKey для указания другого имени.

+ 5.2

добавлено в 5.2 ()

Вдобавок, Eloquent предполагает, что первичный ключ является инкрементным числом, и автоматически приведёт его к типу int. Если вы хотите использовать неинкрементный или нечисловой первичный ключ, задайте открытому свойству PHP$incrementing вашей модели значение PHPfalse.

Отметки времени

По умолчанию Eloquent ожидает наличия в ваших таблицах столбцов updated_at и created_at. Если вы не хотите, чтобы они автоматически обрабатывались в Eloquent, установите свойство PHP$timestamps класса модели в PHPfalse:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Flight extends Model
{
  
/**
   * Определяет необходимость отметок времени для модели.
   *
   * @var bool
   */
  
public $timestamps false;
}

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

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Flight extends Model
{
  
/**
   * Формат хранения отметок времени модели.
   *
   * @var string
   */
  
protected $dateFormat 'U';
}
+ 5.3

добавлено в 5.3 ()

Если вам надо изменить имена столбцов для хранения отметок времени, вы можете задать константы PHPCREATED_AT и PHPUPDATED_AT в своей модели:

PHP
<?php

class Flight extends Model
{
  const 
CREATED_AT 'creation_date';
  const 
UPDATED_AT 'last_update';
}
+ 5.0

добавлено в 5.0 ()

Для настройки формата времени перекройте метод PHPgetDateFormat():

PHP
class User extends Model {

  protected function 
getDateFormat()
  {
    return 
'U';
  }

}

Соединение с БД

По умолчанию модели Eloquent будут использовать основное соединение с БД, настроенное для вашего приложения. Если вы хотите указать другое соединение для модели, используйте свойство PHP$connection:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
Flight extends Model
{
  
/**
   * Название соединения для модели.
   *
   * @var string
   */
  
protected $connection 'connection-name';
}
+ 5.0

добавлено в 5.0 ()

Указание имени соединения с БД

Иногда вам нужно указать, какое подключение должно быть использовано при выполнении запроса Eloquent — просто используйте метод PHPon():

PHP
$user User::on('connection-name')->find(1);

Если вы используете соединения для чтения/записи, вы можете заставить запрос использовать соединение «записи» с помощью следующего метода:

PHP
$user User::onWriteConnection()->find(1);

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

После создания модели и связанной с ней таблицы, вы можете начать получать данные из вашей БД. Каждая модель Eloquent представляет собой мощный конструктор запросов, позволяющий удобно выполнять запросы к связанной таблице. Например:

+ 5.3

добавлено в 5.3 ()

PHP
<?php

use App\Flight;

$flights App\Flight::all();

foreach (
$flights as $flight) {
  echo 
$flight->name;
}
+ 5.2 5.1 5.0

добавлено в 5.2 () 5.1 () 5.0 ()

PHP
<?php

namespace App\Http\Controllers;

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

class 
FlightController extends Controller
{
  
/**
   * Показать список всех доступных рейсов.
   *
   * @return Response
   */
  
public function index()
  {
    
$flights Flight::all();

    return 
view('flight.index', ['flights' => $flights]);
  }
}

Доступ к значениям столбцов

Если у вас есть экземпляр модели Eloquent, вы можете обращаться к значениям столбцов модели, обращаясь к соответствующим свойствам. Например, давайте пройдёмся по каждому экземпляру Flight, возвращённому нашим запросом, и выведем значение столбца name:

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

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

Метод PHPall() в Eloquent возвращает все результаты из таблицы модели. Поскольку модели Eloquent работают как конструктор запросов, вы можете также добавить ограничения в запрос, а затем использовать метод PHPget() для получения результатов:

PHP
$flights App\Flight::where('active'1)
               ->
orderBy('name''desc')
               ->
take(10)
               ->
get();

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

Коллекции

Такие методы Eloquent, как PHPall() и PHPget(), которые получают несколько результатов, возвращают экземпляр Illuminate\Database\Eloquent\Collection. Класс Collection предоставляет множество полезных методов для работы с результатами Eloquent.

PHP
$flights $flights->reject(function ($flight) {
  return 
$flight->cancelled;
});

Само собой, вы также можете просто перебирать такую коллекцию в цикле как массив:

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

Разделение результата на блоки

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

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

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

+ 5.3 5.2

добавлено в 5.3 () 5.2 ()

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

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

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

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

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

Разумеется, кроме получения всех записей указанной таблицы вы можете также получить конкретные записи с помощью PHPfind() и PHPfirst(). Вместо коллекции моделей эти методы возвращают один экземпляр модели:

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

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

добавлено в 5.2 ()

Также вы можете вызвать метод PHPfind() с массивом первичных ключей, который вернёт коллекцию подходящих записей:

PHP
$flights App\Flight::find([123]);

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

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

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

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

Если исключение не поймано, пользователю автоматически посылается HTTP-отклик 404. Нет необходимости писать явные проверки для возврата откликов 404 при использовании этих методов:

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

добавлено в 5.0 ()

Это позволит вам отловить исключение, занести его в журнал и вывести необходимую страницу ошибки. Чтобы поймать исключение PHPModelNotFoundException, добавьте какую-либо логику в ваш файл app/Exceptions/Handler.php.

PHP
use Illuminate\Database\Eloquent\ModelNotFoundException;

class 
Handler extends ExceptionHandler {

    public function 
render($requestException $e)
    {
        if (
$e instanceof ModelNotFoundException)
        {
            
// Ваша логика для ненайденной модели...
        
}

        return 
parent::render($request$e);
    }

}

Построение запросов в моделях Eloquent

PHP
$users User::where('votes''>'100)->take(10)->get();

foreach (
$users as $user)
{
    
var_dump($user->name);
}

Агрегатные функции

Вам также доступны агрегатные функции конструктора запросов, такие как PHPcount(), PHPmax(), PHPsum() и др. Эти методы возвращают соответствующее скалярное значение вместо полного экземпляра модели:

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

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

добавлено в 5.0 ()

Если у вас не получается создать нужный запрос с помощью конструктора, то можно использовать метод PHPwhereRaw():

PHP
$users User::whereRaw('age > ? and votes = 100', [25])->get();

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

Вставки

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

PHP
<?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. При вызове метода PHPsave() запись будет вставлена в таблицу. Отметки времени created_at и PHPupdated_at(t) будут автоматически установлены при вызове PHPsave(), поэтому не надо задавать их вручную.

+ 5.0

добавлено в 5.0 ()

Обычно ваши модели Eloquent содержат автоматические числовые ключи (autoincrementing). Однако если вы хотите использовать собственные ключи, установите свойство PHP$incrementing класса модели в значение PHPfalse.

Изменения

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

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

$flight->name 'New Flight Name';

$flight->save();

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

Изменения можно выполнить для нескольких моделей, которые соответствуют указанному запросу. В этом примере все рейсы, которые отмечены как active и имеют destination равное San Diego, будут отмечены как delayed:

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

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

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

Массовое заполнение

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

Уязвимость массового заполнения проявляется, когда пользователь передаёт с помощью запроса неподходящий HTTP-параметр, и вы не ожидаете, что этот параметр изменит столбец в вашей БД. Например, злоумышленник может послать в HTTP-запросе параметр is_admin, который затем передаётся в метод PHPcreate() вашей модели, позволяя пользователю повысить свои привилегии до администратора.

Поэтому, для начала надо определить, для каких атрибутов разрешить массовое назначение. Это делается с помощью свойства модели PHP$fillable. Например, давайте разрешим массовое назначение атрибута name нашей модели Flight:

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

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

Теперь мы можем использовать метод PHPcreate() для вставки новой записи в БД. Метод PHPcreate() возвращает сохранённый экземпляр модели:

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

добавлено в 5.3 ()

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

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

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

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

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

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

добавлено в 5.3 ()

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

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

PHP
%%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11)
.(
alert)
При использовании %%$guarded%%, вы по-прежнему не должны передавать %%Input::get()%% или любые сырые массивы пользовательского ввода в методы %%save()%% и %%update()%%, потому что может быть обновлён любой незащищённый столбец.

**
Защита всех атрибутов от массового заполнения**

Вы также можете запретить массовое заполнение всем атрибутамиспользуя символ %%*%%:
%%
protected 
$guarded = ['*'];
%%

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

firstOrCreate / firstOrNew

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

Метод PHPfirstOrNew() как и PHPfirstOrCreate() пытается найти в БД запись, соответствующую указанным атрибутам. Однако если модель не найдена, будет возвращён новый экземпляр модели. Учтите, что эта модель ещё не помещена в БД. Вам надо вызвать метод PHPsave() вручную, чтобы сохранить её:

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

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

добавлено в 5.3 ()

updateOrCreate

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

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

добавлено в 5.0 ()

После сохранения или создания новой модели, использующей автоматические (autoincrementing) ID, вы можете получать ID объектов, обращаясь к их атрибуту PHPid:

PHP
$insertedId $user->id;

Сохранение модели и её отношений

Иногда вам может быть нужно сохранить не только модель, но и все её отношения. Для этого просто используйте метод PHPpush():

PHP
$user->push();

Вы также можете выполнять обновления в виде запросов к набору моделей:

PHP
$affectedRows User::where('votes''>'100)->update(['status' => 2]);

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

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

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

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

$flight->delete();

Удаление модели по ключу

В предыдущем примере мы получили модель из БД перед вызовом метода PHPdelete(). Но если вы знаете первичный ключ модели, вы можете удалить модель, не получая её. Для этого вызовите метод PHPdestroy():

PHP
App\Flight::destroy(1);

App\Flight::destroy([123]);

App\Flight::destroy(123);

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

Конечно, вы также можете выполнить оператор удаления на наборе моделей. В этом примере мы удалим все рейсы, отмеченные неактивными. Подобно массовому обновлению, массовое удаление не вызовет никаких событий для удаляемых моделей:

PHP
$deletedRows App\Flight::where('active'0)->delete();

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

+ 5.0

добавлено в 5.0 ()

Обновление только времени изменения модели

Если вам нужно просто обновить время изменения записи — используйте метод PHPtouch():

PHP
$user->touch();

Мягкое удаление

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

PHP
<?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 в вашу таблицу. Для этого используется метод PHPsoftDeletes() конструктора таблиц:

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

Теперь когда вы вызовите метод PHPdelete(), поле deleted_at будет установлено в значение текущей даты и времени. При запросе моделей, использующих мягкое удаление, «удалённые» модели не будут включены в результат запроса.

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

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

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

Включение удалённых моделей в результат выборки

Как было сказано, мягко удалённые модели автоматически исключаются из результатов запроса. Для отображения всех моделей, в том числе удалённых, используйте метод PHPwithTrashed():

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

Метод PHPwithTrashed может быть использован в отношениях:

PHP
$flight->history()->withTrashed()->get();
+ 5.1 5.0

добавлено в 5.1 () 5.0 ()

Рекомендации для условия WHERE

При добавлении в запрос мягко удалённых моделей условий orWhere всегда используйте сложные условия WHERE для их логической группировки. Например:

PHP
User::where(function($query) {
        
$query->where('name''=''John')
              ->
orWhere('votes''>'100);
        })
        ->
get();

Таким образом получится следующий SQL-запрос:

sqlselect * from `users` where `users`.`deleted_at` is null and (`name` = 'John' or `votes` > 100)

Если условия orWhere не сгруппированы, то получится следующий SQL-запрос, который будет содержать мягко удалённые записи:

sqlselect * from `users` where `users`.`deleted_at` is null and `name` = 'John' or `votes` > 100

Получение только мягко удалённых моделей

Если вы хотите получить только мягко удалённые модели, вызовите метод PHPonlyTrashed():

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

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

Иногда необходимо восстановить мягко удалённую модель. Для восстановления мягко удалённой модели в активное состояние используется метод PHPrestore():

PHP
$flight->restore();

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

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

Как и метод PHPwithTrashed, метод PHPrestore() можно использовать и в отношениях:

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

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

Если вы хотите полностью удалить модель из БД, используйте метод PHPforceDelete():

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

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

Заготовки запросов

+ 5.3 5.2

добавлено в 5.3 () 5.2 ()

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

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

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

Писать глобальные заготовки просто. Определите класс, реализующий интерфейс Illuminate\Database\Eloquent\Scope. Этот интерфейс требует реализации одного метода: PHPapply(). Метод PHPapply() может добавить к запросу ограничение PHPwhere при необходимости:

PHP
<?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 $builderModel $model)
  {
    
$builder->where('age''>'200);
  }
}

В Laravel-приложении по умолчанию нет определённой папки для хранения заготовок, поэтому вы можете создать свою папку Scopes в папке app вашего приложения.

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

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

PHP
<?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);
  }
}

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

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

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

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

PHP
<?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);
    });
  }
}

Первый аргумент PHPaddGlobalScope() служит идентификатором для удаления заготовки:

PHP
User::withoutGlobalScope('age')->get();

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

Если вы хотите удалить глобальную заготовку для данного запроса, то можете использовать метод PHPwithoutGlobalScope(). Этот метод принимает единственный аргумент — имя класса глобальной заготовки:

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

Если вы хотите удалить несколько или все глобальные заготовки, то можете использовать метод PHPwithoutGlobalScopes():

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

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

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

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

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
User extends Model
{
  
/**
   * Заготовка запроса популярных пользователей.
   *
   * @param \Illuminate\Database\Eloquent\Builder $query
   * @return \Illuminate\Database\Eloquent\Builder
   */
  
public function scopePopular($query)
  {
    return 
$query->where('votes''>'100);
  }

  
/**
   * Заготовка запроса активных пользователей.
   *
   * @param \Illuminate\Database\Eloquent\Builder $query
   * @return \Illuminate\Database\Eloquent\Builder
   */
  
public function scopeActive($query)
  {
    return 
$query->where('active'1);
  }
}

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

Когда заготовка определена, вы можете вызывать методы заготовки при запросах к модели. Но теперь вам не нужно использовать префикс scope. Вы можете даже сцеплять вызовы разных заготовок, например:

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

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

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

PHP
<?php

namespace App;

use 
Illuminate\Database\Eloquent\Model;

class 
User extends Model
{
  
/**
   * Заготовка запроса пользователей определённого типа.
   *
   * @param \Illuminate\Database\Eloquent\Builder $query
   * @param mixed $type
   * @return \Illuminate\Database\Eloquent\Builder
   */
  
public function scopeOfType($query$type)
  {
    return 
$query->where('type'$type);
  }
}

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

PHP
$users App\User::ofType('admin')->get();
+ 5.0

добавлено в 5.0 ()

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

Иногда вам требуется определить заготовку, которая будет применяться для всех выполняемых в модели запросов. По сути так и работает «мягкое удаление» в Eloquent. Глобальные заготовки определяются с помощью комбинации типажей PHP и реализации Illuminate\Database\Eloquent\ScopeInterface.

Сначала определим типаж. В этом примере мы будем использовать встроенный в Laravel PHPSoftDeletes:

PHP
trait SoftDeletes {

    
/**
     * Загрузка типажа мягкого удаления для модели.
     *
     * @return void
     */
    
public static function bootSoftDeletes()
    {
        static::
addGlobalScope(new SoftDeletingScope);
    }

}

Если в модели Eloquent используется типаж, содержащий соответствующий соглашению по названиям bootNameOfTrait метод, тогда этот метод типажа будет вызываться при загрузке модели Eloquent. Это даёт вам возможность зарегистрировать глобальную заготовку, или сделать ещё что-либо необходимое. Заготовка должна реализовывать PHPScopeInterface, который содержит два метода: PHPapply() и PHPremove().

Метод PHPapply() принимает объект конструктора запросов Illuminate\Database\Eloquent\Builder и модель, к которой он применяется, и отвечает за добавление любых дополнительных операторов where, которые необходимы заготовке.
Метод PHPremove() также принимает объект Builder и модель, и отвечает за отмену действий, произведённых методом PHPapply(). Другими словами, PHPremove() должен удалить добавленные операторы where (или любые другие).
Поэтому для нашей PHPSoftDeletingScope методы будут такими:

PHP
/**
 * Применение заготовки к указанному конструктору запросов Eloquent.
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @return void
 */
public function apply(Builder $builderModel $model)
{
    
$builder->whereNull($model->getQualifiedDeletedAtColumn());

    
$this->extend($builder);
}

/**
 * Удаление заготовки из указанного конструктора запросов Eloquent.
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @return void
 */
public function remove(Builder $builderModel $model)
{
    
$column $model->getQualifiedDeletedAtColumn();

    
$query $builder->getQuery();

    foreach ((array) 
$query->wheres as $key => $where)
    {
        
// Если оператор where ограничивает мягкое удаление данных, мы удалим его из
        // запроса и сбросим ключи в операторах where. Это позволит разработчику
        // включить удалённую модель в отношения результирующего набора, который загружается "лениво".
        
if ($this->isSoftDeleteConstraint($where$column))
        {
            unset(
$query->wheres[$key]);

            
$query->wheres array_values($query->wheres);
        }
    }
}

Отношения

В документации Laravel 5.1+ данный раздел вынесен в отдельную статью — Отношения. — прим. пер.

Конечно, ваши таблицы скорее всего как-то связаны с другими таблицами БД. Например, статья в блоге может иметь много комментариев, а заказ может быть связан с оставившим его пользователем. Eloquent упрощает работу и управление такими отношениями. Laravel поддерживает многие типы связей:

  1. Один к одному
  2. Один ко многим
  3. Многие ко многим
  4. Ко многим через
  5. Полиморфические связи
  6. Полиморфические связи «многие ко многим»

Один к одному

Создание связи «один к одному»

Связь вида «один к одному» является очень простой. К примеру, модель PHPUser может иметь один PHPPhone. Мы можем определить такое отношение в Eloquent:

PHP
class User extends Model {

    public function 
phone()
    {
        return 
$this->hasOne('App\Phone');
    }

}

Первый параметр, передаваемый PHPhasOne(), — имя связанной модели. Как только отношение установлено, вы можете получить к нему доступ через динамические свойства Eloquent:

PHP
$phone User::find(1)->phone;

Сгенерированный SQL имеет такой вид:

sqlselect * from users where id = 1

select * from phones where user_id = 1

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

PHP
return $this->hasOne('App\Phone''foreign_key');

return 
$this->hasOne('App\Phone''foreign_key''local_key');

Создание обратного отношения

Для создания обратного отношения в модели PHPPhone используйте метод PHPbelongsTo() («принадлежит к»):

PHP
class Phone extends Model {

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

}

В примере выше Eloquent будет искать поле user_id в таблице phones. Если вы хотите назвать внешний ключ по другому, передайте это имя вторым параметром в метод PHPbelongsTo():

PHP
class Phone extends Model {

    public function 
user()
    {
        return 
$this->belongsTo('App\User''local_key');
    }

}

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

PHP
class Phone extends Model {

    public function 
user()
    {
        return 
$this->belongsTo('App\User''local_key''parent_key');
    }

}

Один ко многим

Примером отношения «один ко многим» является статья в блоге, которая имеет «много» комментариев. Вы можете смоделировать это отношение таким образом:

PHP
class Post extends Model {

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

}

Теперь мы можем получить все комментарии с помощью динамического свойства:

PHP
$comments Post::find(1)->comments;

Если вам нужно добавить ограничения на получаемые комментарии, можно вызвать метод PHPcomments() и продолжить добавлять условия:

PHP
$comments Post::find(1)->comments()->where('title''=''foo')->first();

И опять вы можете передать второй параметр в метод PHPhasMany() для перекрытия стандартного имени ключа. И как и для отношения «hasOne» также может быть указан локальный столбец:

PHP
return $this->hasMany('App\Comment''foreign_key');

return 
$this->hasMany('App\Comment''foreign_key''local_key');

Определение обратного отношения

Для определения обратного отношения используйте метод PHPbelongsTo():

PHP
class Comment extends Model {

    public function 
post()
    {
        return 
$this->belongsTo('App\Post');
    }

}

Многие ко многим

Отношения типа «многие ко многим» — более сложные, чем остальные виды отношений. Примером может служить пользователь, имеющий много ролей, где роли также относятся ко многим пользователям. Например, один пользователь может иметь роль admin. Нужны три таблицы для этой связи: users, roles и role_user. Название таблицы role_user происходит от упорядоченных по алфавиту имён связанных моделей, она должна иметь поля user_id и role_id.

Вы можете определить отношение «многие ко многим» через метод PHPbelongsToMany():

PHP
class User extends Model {

    public function 
roles()
    {
        return 
$this->belongsToMany('App\Role');
    }

}

Теперь мы можем получить роли через модель PHPUser:

PHP
$roles User::find(1)->roles;

Вы можете передать второй параметр к методу PHPbelongsToMany() с указанием имени связующей (pivot) таблицы вместо стандартной:

PHP
return $this->belongsToMany('App\Role''user_roles');

Вы также можете перекрыть имена ключей по умолчанию:

PHP
return $this->belongsToMany('App\Role''user_roles''user_id''foo_id');

Конечно, вы можете определить и обратное отношение на модели PHPRole:

PHP
class Role extends Model {

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

}

Ко многим через

Связь «ко многим через» обеспечивает удобный короткий путь для доступа к удалённым отношениям через промежуточные. Например, модель Country может иметь много Post через модель User. Таблицы для этих отношений будут выглядеть так:

confcountries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

Несмотря на то, что таблица posts не содержит столбца country_id, отношение «hasManyThrough» позволит нам получить доступ к posts через country с помощью PHP$country->posts. Давайте определим отношения:

PHP
class Country extends Model {

    public function 
posts()
    {
        return 
$this->hasManyThrough('App\Post''App\User');
    }

}

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

PHP
class Country extends Model {

    public function 
posts()
    {
        return 
$this->hasManyThrough('App\Post''App\User''country_id''user_id');
    }

}

Полиморфические отношения

Полиморфические отношения позволяют модели быть связанной с более, чем одной моделью. Например, может быть модель PHPPhoto, содержащая записи, принадлежащие к моделям PHPStaff (сотрудники) и PHPOrder. Мы можем создать такое отношение таким образом:

PHP
class Photo extends Model {

    public function 
imageable()
    {
        return 
$this->morphTo();
    }

}

class 
Staff extends Model {

    public function 
photos()
    {
        return 
$this->morphMany('App\Photo''imageable');
    }

}

class 
Order extends Model {

    public function 
photos()
    {
        return 
$this->morphMany('App\Photo''imageable');
    }

}

Чтение полиморфической связи

Теперь мы можем получить фотографии и для сотрудника, и для заказа:

PHP
$staff Staff::find(1);

foreach (
$staff->photos as $photo)
{
    
//
}

Однако настоящая «магия» полиморфизма происходит при чтении связи на модели PHPPhoto:

PHP
$photo Photo::find(1);

$imageable $photo->imageable;

Отношение PHPimageable модели PHPPhoto вернёт либо объект PHPStaff, либо объект PHPOrder в зависимости от типа модели, которой принадлежит фотография.

Структура таблиц полиморфической связи

Чтобы понять, как это работает, давайте изучим структуру БД для полиморфического отношения:

confstaff
    id - integer
    name - string

orders
    id - integer
    price - integer

photos
    id - integer
    path - string
    imageable_id - integer
    imageable_type - string

Главные поля, на которые нужно обратить внимание: imageable_id и imageable_type в таблице photos. Первое содержит ID владельца, в нашем случае — заказа или персонала, а второе — имя класса-модели владельца. Это позволяет ORM определить, какой класс модели должен быть возвращён при использовании отношения PHPimageable.

Полиморфические связи многие ко многим

Структура таблиц полиморфической связи многие ко многим

В дополнение к традиционным полиморфическим связям вы можете также задать полиморфические связи многие ко многим. Например, модели блогов Post и Video могут разделять полиморфическую связь с моделью Tag. Во-первых, давайте рассмотрим структуру таблиц:

confposts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_id - integer
    taggable_type - string

Далее, мы готовы к установке связи с моделью. Обе модели Post и Video будут иметь связь «morphToMany» через метод PHPtags:

PHP
class Post extends Model {

    public function 
tags()
    {
        return 
$this->morphToMany('App\Tag''taggable');
    }

}

Модель Tag может определить метод для каждого из своих отношений:

PHP
class Tag extends Model {

    public function 
posts()
    {
        return 
$this->morphedByMany('App\Post''taggable');
    }

    public function 
videos()
    {
        return 
$this->morphedByMany('App\Video''taggable');
    }

}

Запросы к отношениям

Проверка связей при выборке

При чтении отношений модели вам может быть нужно ограничить результаты в зависимости от существования связи. Например, вы хотите получить все статьи в блоге, имеющие хотя бы один комментарий. Для этого можно использовать метод PHPhas():

PHP
$posts Post::has('comments')->get();

Вы также можете указать оператор и число:

PHP
$posts Post::has('comments''>='3)->get();

Можно конструировать вложенные операторы PHPhas с помощью точечной нотации:

PHP
$posts Post::has('comments.votes')->get();

Если вам нужно ещё больше возможностей, вы можете использовать методы PHPwhereHas и PHPorWhereHas, чтобы поместить условия "where" в ваши запросы has:

PHP
$posts Post::whereHas('comments', function($q)
{
    
$q->where('content''like''foo%');

})->
get();

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

Eloquent позволяет вам читать отношения через динамические свойства. Eloquent автоматически определит используемую связь и даже вызовет PHPget() для связей «один ко многим» и PHPfirst() — для связей «один к одному». Эта связь будет доступна через динамическое свойство с тем же именем. К примеру, для следующей модели PHP$phone:

PHP
class Phone extends Model {

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

}

$phone Phone::find(1);

Вместо того, чтобы получить e-mail пользователя так:

PHP
echo $phone->user()->first()->email;

...вызов может быть сокращён до такого:

PHP
echo $phone->user->email;

Внимание: Отношения, которые возвращают много результатов, вернут экземпляр класса PHPIlluminate\Database\Eloquent\Collection.

Активная загрузка

Активная загрузка (eager loading) призвана устранить проблему запросов N + 1. Например, представьте, что у нас есть модель PHPBook со связью к модели PHPAuthor. Отношение определено как:

PHP
class Book extends Model {

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

}

Теперь предположим, у нас есть такой код:

PHP
foreach (Book::all() as $book)
{
    echo 
$book->author->name;
}

Цикл выполнит один запрос для получения всех книг в таблице, а затем будет выполнять по одному запросу на каждую книгу для получения автора. Таким образом, если у нас 25 книг, то потребуется 26 запросов.

К счастью, мы можем использовать активную загрузку для кардинального уменьшения числа запросов. Отношение будет активно загружено, если оно было указано при вызове метода PHPwith():

PHP
foreach (Book::with('author')->get() as $book)
{
    echo 
$book->author->name;
}

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

sqlselect * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

Разумное использование активной загрузки поможет сильно повысить производительность вашего приложения.

Конечно, вы можете загрузить несколько отношений одновременно:

PHP
$books Book::with('author''publisher')->get();

Вы даже можете загрузить вложенные отношения:

PHP
$books Book::with('author.contacts')->get();

В примере выше связь PHPauthor будет активно загружена вместе со связью PHPcontacts модели автора.

Ограничения активной загрузки

Иногда вам может быть нужно не только активно загрузить отношение, но также указать условие для его загрузки:

PHP
$users User::with(['posts' => function($query)
{
    
$query->where('title''like''%первое%');

}])->
get();

В этом примере мы загружаем сообщения пользователя, но только те, заголовок которых содержит подстроку «первое».

Конечно, функции-замыкания активной загрузки не ограничиваются только условиями. Вы также можете применить упорядочивание:

PHP
$users User::with(['posts' => function($query)
{
    
$query->orderBy('created_at''desc');

}])->
get();

Ленивая активная загрузка

Можно активно загрузить связанные модели напрямую из уже созданного набора объектов моделей. Это может быть полезно при определении во время выполнения, требуется ли такая загрузка или нет, или в комбинации с кэшированием.

PHP
$books Book::all();

$books->load('author''publisher');

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

PHP
$books->load(['author' => function($query)
{
    
$query->orderBy('published_date''asc');
}]);

Вставка связанных моделей

Создание связанной модели

Часто вам нужно будет добавить связанную модель. Например, вы можете создать новый комментарий к сообщению. Вместо явного указания значения для поля post_id вы можете вставить модель напрямую через её родителя — модели PHPPost:

PHP
$comment = new Comment(['message' => 'A new comment.']);

$post Post::find(1);

$comment $post->comments()->save($comment);

В этом примере поле post_id вставленного комментария автоматически получит значение ID своей статьи.

Сохранить несколько связанных моделей можно так:

PHP
$comments = [
    new 
Comment(['message' => 'A new comment.']),
    new 
Comment(['message' => 'Another comment.']),
    new 
Comment(['message' => 'The latest comment.'])
];

$post Post::find(1);

$post->comments()->saveMany($comments);

Связывание моделей (belongs to)

При обновлении связей PHPbelongsTo («принадлежит к») вы можете использовать метод PHPassociate(). Он установит внешний ключ на дочерней модели:

PHP
$account Account::find(10);

$user->account()->associate($account);

$user->save();

Вставка связанных моделей (многие ко многим)

Вы также можете вставлять связанные модели при работе с отношениями многие ко многим. Продолжим использовать наши модели PHPUser и PHPRole в качестве примеров. Вы можете легко привязать новые роли к пользователю методом PHPattach().

Связывание моделей «многие ко многим»

PHP
$user User::find(1);

$user->roles()->attach(1);

Вы также можете передать массив атрибутов, которые должны быть сохранены в связующей (pivot) таблице для этого отношения:

PHP
$user->roles()->attach(1, ['expires' => $expires]);

Конечно, существует противоположность PHPattach()PHPdetach():

PHP
$user->roles()->detach(1);

Оба метода PHPattach() и PHPdetach() также принимают в качестве параметров массивы ID:

PHP
$user User::find(1);

$user->roles()->detach([123]);

$user->roles()->attach([=> ['attribute1' => 'value1'], 23]);

Использование PHPsync() для привязки моделей «многие ко многим»

Вы также можете использовать метод PHPsync() для привязки связанных моделей. Этот метод принимает массив ID, которые должны быть сохранены в связующей таблице. Когда операция завершится, только переданные ID будут существовать в промежуточной таблице для данной модели:

PHP
$user->roles()->sync([123]);

Добавление данных для связующей таблицы при синхронизации

Вы также можете связать другие связующие таблицы с нужными ID:

PHP
$user->roles()->sync([=> ['expires' => true]]);

Иногда вам может быть нужно создать новую связанную модель и добавить её одной командой. Для этого вы можете использовать метод PHPsave():

PHP
$role = new Role(['name' => 'Editor']);

User::find(1)->roles()->save($role);

В этом примере новая модель PHPRole будет сохранена и привязана к модели PHPUser. Вы можете также передать массив атрибутов для помещения в связующую таблицу:

PHP
User::find(1)->roles()->save($role, ['expires' => $expires]);

Обновление времени владельца

Когда модель принадлежит другой посредством PHPbelongsTo() — например, PHPComment, принадлежащий PHPPost — иногда нужно обновить время изменения владельца при обновлении связанной модели. Например, при изменении модели PHPComment вы можете обновлять поле updated_at её модели PHPPost. Eloquent делает этот процесс простым — просто добавьте свойство PHP$touches, содержащее имена всех отношений с моделями-потомками:

PHP
class Comment extends Model {

    protected 
$touches = ['post'];

    public function 
post()
    {
        return 
$this->belongsTo('App\Post');
    }

}

Теперь при обновлении PHPComment владелец PHPPost также обновит своё поле updated_at:

PHP
$comment Comment::find(1);

$comment->text 'Изменение этого комментария!';

$comment->save();

Работа со связующими таблицами

Как вы уже узнали, работа отношения многие ко многим требует наличия промежуточной таблицы. Например, предположим, что наш объект PHPUser имеет множество связанных объектов PHPRole. После чтения отношения мы можем прочитать таблицу PHPpivot на обеих моделях:

PHP
$user User::find(1);

foreach (
$user->roles as $role)
{
    echo 
$role->pivot->created_at;
}

Заметьте, что каждая модель PHPRole автоматически получила атрибут PHPpivot. Этот атрибут содержит модель, представляющую промежуточную таблицу, и она может быть использована как любая другая модель Eloquent.

По умолчанию, только ключи будут представлены в объекте PHPpivot. Если ваша связующая таблица содержит другие поля, вы можете указать их при создании отношения:

PHP
return $this->belongsToMany('App\Role')->withPivot('foo''bar');

Теперь атрибуты foo и bar будут также доступны на объекте PHPpivot модели PHPRole.

Если вы хотите автоматически поддерживать поля created_at и updated_at актуальными, используйте метод PHPwithTimestamps() при создании отношения:

PHP
return $this->belongsToMany('App\Role')->withTimestamps();

Удаление всех связующих записей

Для удаления всех записей в связующей таблице можно использовать метод PHPdetach():

PHP
User::find(1)->roles()->detach();

Заметьте, что эта операция не удаляет записи из таблицы roles, а только из связующей таблицы.

Обновление записи в связующей таблице

Иногда необходимо обновить связующую таблицу не отвязывая её. Для обновления вашей связующей таблицы на месте используйте метод PHPupdateExistingPivot():

PHP
User::find(1)->roles()->updateExistingPivot($roleId$attributes);

Определение собственной связующей модели

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

PHP
public function newPivot(Model $parent, array $attributes$table$exists)
{
    return new 
YourCustomPivot($parent$attributes$table$exists);
}

Коллекции

В документации Laravel 5.1+ данный раздел вынесен в отдельную статью — Коллекции. — прим. пер.

Все методы Eloquent, возвращающие набор моделей — либо через PHPget(), либо через отношения — возвращают объект-коллекцию. Этот объект реализует стандартный интерфейс PHP PHPIteratorAggregate, что позволяет ему быть использованным в циклах как массив. Однако этот объект также имеет набор других полезных методов для работы с результатом запроса.

Проверка на существование ключа в коллекции

Например, мы можем выяснить, содержит ли результат запись с определённым первичным ключом, методом PHPcontains():

PHP
$roles User::find(1)->roles;

if (
$roles->contains(2))
{
    
//
}

Коллекции также могут быть преобразованы в массив или строку JSON:

PHP
$roles User::find(1)->roles->toArray();

$roles User::find(1)->roles->toJson();

Если коллекция преобразуется в строку, результатом будет JSON-выражение:

PHP
$roles = (string) User::find(1)->roles;

Проход по элементам коллекции

Коллекции Eloquent имеют несколько полезных методов для прохода и фильтрации содержащихся в них элементов:

PHP
$roles $user->roles->each(function($role)
{
    
//
});

Фильтрация элементов коллекции

При фильтрации коллекций передаваемая функция будет использована как функция обратного вызова для array_filter.

PHP
$users $users->filter(function($user)
{
    return 
$user->isAdmin();
});

Внимание: При фильтрации коллекций и конвертации их в JSON попробуйте сначала вызвать функцию PHPvalues для сброса ключей массива.

Применение функции обратного вызова к каждому объекту коллекции

PHP
$roles User::find(1)->roles;

$roles->each(function($role)
{
    
//
});

Сортировка коллекции по значению

PHP
$roles $roles->sortBy(function($role)
{
    return 
$role->created_at;
});

$roles $roles->sortByDesc(function($role)
{
    return 
$role->created_at;
});

Сортировка коллекции по значению

PHP
$roles $roles->sortBy('created_at');

$roles $roles->sortByDesc('created_at');

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

Иногда вам может быть нужно получить собственный объект PHPCollection со своими методами. Вы можете указать его при определении модели Eloquent, перекрыв метод PHPnewCollection():

PHP
class User extends Model {

    public function 
newCollection(array $models = [])
    {
        return new 
CustomCollection($models);
    }

}

Читатели и преобразователи

В документации Laravel 5.1+ данный раздел вынесен в отдельную статью — Преобразователи. — прим. пер.

Объявление читателя

Eloquent содержит мощный механизм для преобразования атрибутов модели при их чтении и записи. Просто объявите в её классе метод PHPgetFooAttribute(). Помните, что имя метода должно следовать соглашению camelCase, даже если поля таблицы используют соглашение snake-case (он же — «стиль Си», с подчёркиваниями — прим. пер.):

PHP
class User extends Model {

    public function 
getFirstNameAttribute($value)
    {
        return 
ucfirst($value);
    }

}

В примере выше поле first_name теперь имеет читателя (accessor). Заметьте, что оригинальное значение атрибута передаётся методу в виде параметра.

Объявление преобразователя

Преобразователи (mutators) объявляются подобным образом:

PHP
class User extends Model {

    public function 
setFirstNameAttribute($value)
    {
        
$this->attributes['first_name'] = strtolower($value);
    }

}

Преобразователи дат

По умолчанию Eloquent преобразует поля created_at и updated_at в объекты Carbon, которые предоставляют множество полезных методов, расширяя стандартный класс PHP DateTime.

Вы можете указать, какие поля будут автоматически преобразованы, и даже полностью отключить преобразование, перекрыв метод PHPgetDates() класса модели:

PHP
public function getDates()
{
    return [
'created_at'];
}

Когда поле является датой, вы можете установить его в число-оттиск времени формата Unix (timestamp), строку даты в формате Y-m-d, строку даты-времени и, конечно, экземпляр объекта PHPDateTime или PHPCarbon.

Чтобы полностью отключить преобразование дат, просто верните пустой массив из метода PHPgetDates():

PHP
public function getDates()
{
    return [];
}

Изменение атрибутов

Если у вас есть несколько атрибутов, которые вы хотите всегда конвертировать в другой формат данных, вы можете добавить атрибут в свойство PHPcasts вашей модели. Иначе вам нужно будет определять преобразователь для каждого из атрибутов, а это может отнять много времени.

PHP
/**
 * Атрибуты, которые нужно преобразовать в нативный тип.
 *
 * @var array
 */
protected $casts = [
  
'is_admin' => 'boolean',
];

Теперь при каждом обращении атрибут PHPis_admin будет преобразовываться в PHPboolean, даже если базовое значение сохранено в базе данных как PHPinteger. Другие поддерживаемые типы для преобразования: PHPinteger, PHPreal, PHPfloat, PHPdouble, PHPstring, PHPboolean, PHPobject и PHParray.

Преобразование типа PHParray особенно полезно для работы с полями, которые сохранены как сериализованный JSON. Например, если у вашей базы данных есть поле типа TEXT, которое содержит сериализованный JSON, добавление преобразования в тип PHParray к атрибуту автоматически десериализует атрибут в массив PHP при обращении к нему через модель Eloquent:

PHP
/**
 * Атрибуты, которые нужно преобразовать в нативный тип.
 *
 * @var array
 */
protected $casts = [
  
'options' => 'array',
];

Теперь, когда вы используете модель Eloquent:

PHP
$user User::find(1);

// $options - массив...
$options $user->options;

// options автоматически сериализуются обратно в JSON...
$user->options = ['foo' => 'bar'];

События моделей

Модели Eloquent инициируют несколько событий, что позволяет вам добавить к ним свои обработчики с помощью следующих методов: PHPcreating(), PHPcreated(), PHPupdating(), PHPupdated(), PHPsaving(), PHPsaved(), PHPdeleting(), PHPdeleted(), PHPrestoring(), PHPrestored(). События позволяют вам легко выполнять код при каждом сохранении или изменении класса конкретной модели в БД.

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

Например, давайте определим слушателя событий Eloquent в сервис-провайдере. В нашем слушателе событий мы будем вызывать метод PHPisValid() для данной модели, и возвращать PHPfalse, если она не прошла проверку. Возврат PHPfalse из слушателя событий Eloquent отменит операции save/update:

PHP
<?php

namespace App\Providers;

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

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

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

добавлено в 5.0 ()

Отмена сохранения модели через события

Если обработчики PHPcreating, PHPupdating, PHPsaving или PHPdeleting вернут значение PHPfalse, то действие будет отменено:

PHP
User::creating(function($user)
{
    if ( ! 
$user->isValid()) return false;
});
+ 5.3

добавлено в 5.3 ()

Наблюдатели моделей

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

PHP
<?php

namespace App\Observers;

use 
App\User;

class 
UserObserver
{
  
/**
   * Прослушивание события создания пользователя.
   *
   * @param  User  $user
   * @return void
   */
  
public function created(User $user)
  {
    
//
  
}

  
/**
   * Прослушивание события удаления пользователя.
   *
   * @param  User  $user
   * @return void
   */
  
public function deleting(User $user)
  {
    
//
  
}
}

Для регистрации наблюдателя используйте метод PHPobserve() на наблюдаемой модели. Вы можете зарегистрировать наблюдателей в методе PHPboot() одного из ваших сервис-провайдеров. В этом примере мы зарегистрируем наблюдателя в AppServiceProvider:

PHP
<?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()
  {
    
//
  
}
}
+ 5.0

добавлено в 5.0 ()

Для того, чтобы держать все обработчики событий моделей вместе, вы можете зарегистрировать наблюдателя (observer). Объект-наблюдатель может содержать методы, соответствующие различным событиям моделей. Например, методы PHPcreating(), PHPupdating() и PHPsaving(), а также любые другие методы, соответствующие именам событий.

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

PHP
class UserObserver {

    public function 
saving($model)
    {
        
//
    
}

    public function 
saved($model)
    {
        
//
    
}

}

Вы можете зарегистрировать его используя метод PHPobserve():

PHP
User::observe(new UserObserver);

Генерация URL модели

Когда вы передаёте модель в методы PHProute() и PHPaction(), её первичный ключ вставляется в сгенерированный URI. Например:

PHP
Route::get('user/{user}''UserController@show');

action('UserController@show', [$user]);

В этом примере свойство PHP$user->id будет подставлено вместо строки-переменной PHP{user} в сгенерированный URL. Но если вы хотите использовать другое свойств вместо ID, переопределите метод PHPgetRouteKey() в своей модели:

PHP
public function getRouteKey()
{
    return 
$this->slug;
}

Преобразование в массивы и JSON

В документации Laravel 5.1+ данный раздел вынесен в отдельную статью — Сериализация. — прим. пер.

Преобразование модели в массив

При создании JSON API вам часто потребуется преобразовывать модели и отношения к массивам или выражениям JSON. Eloquent содержит методы для выполнения этих задач. Для преобразования модели или загруженного отношения в массив можно использовать метод PHPtoArray():

PHP
$user User::with('roles')->first();

return 
$user->toArray();

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

PHP
return User::all()->toArray();

Преобразование модели к JSON

Для преобразования модели к JSON вы можете использовать метод PHPtoJson():

PHP
return User::find(1)->toJson();

Возврат модели из маршрута

Обратите внимание, что если модель преобразуется к строке, результатом также будет JSON, — это значит, что вы можете возвращать объекты Eloquent напрямую из ваших маршрутов!

PHP
Route::get('users', function()
{
    return 
User::all();
});

Скрытие атрибутов при преобразовании в массив или JSON

Иногда вам может быть нужно ограничить список атрибутов, включённых в преобразованный массив или JSON-строку — например, скрыть пароли. Для этого определите в классе модели свойство PHPhidden:

PHP
class User extends Model {

    protected 
$hidden = ['password'];

}

Внимание: При скрытии отношений используйте имя PHPmethod отношения, а не имя для динамического доступа.

Вы также можете использовать атрибут PHP$visible для указания разрешённых полей:

PHP
protected $visible = ['first_name''last_name'];

Иногда вам может быть нужно добавить поле, которое не существует в таблице. Для этого просто определите для него читателя:

PHP
public function getIsAdminAttribute()
{
    return 
$this->attributes['admin'] == 'yes';
}

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

PHP
protected $appends = ['is_admin'];

Как только атрибут был добавлен к списку PHPappends, он будет включён в массивы и выражения JSON, образованные от этой модели. Атрибуты в массиве PHPappends соответствуют настройкам модели PHPvisible и PHPhidden.

Комментарии (6)

Max_G

Доки по FormBuilder ещё не переведены или в Laravel 5 этого нет или по-другому называется?

Prowler

Дочитал до «глобальной заготовки» и ничего не понял :-) Ушел читать английский вариант и сразу обнаружилась ошибочка небольшая.
Здесь: Illuminate\Database\Eloquent\ScopeInterface
В оригинале: Illuminate\Database\Eloquent\Scope interface
Т.е. интерфейс называется Scope а не Scope interface, выходит

Proger_XP

Ошибки нет, участок со PHPScopeInterface выделен в специальный блок для версии 5.0. В 5.1 и выше это был изменено, в чём можно убедиться в той же английской версии для ветки 5.0.

Prowler

Понятно, спасибо. Не заметил вставки о версии описываемой в статье.

iNikolay

Что-то не получается создать миграцию при создании модели.

Ввожу команду

php artisan make:model MyModel -m

Модель создаётся

Model created successfully.

Но далее следует ошибка (что файл не найден - хотя он ведь должен создаваться?) и миграция не создаётся:

[ErrorException]
include(/home/projects/web/laravel/public_html/vendor/composer/../
../database/migrations/2017_05_02_110722_create_calculators_table.php): failed to open stream: No such file or directory

Подскажите, что я делаю не так? :(

exbatek

Иногда бывают случаи, когда вам может понадобится не только pivot-таблица, но и pivot-модель, для того чтобы из родительской модели по цепочке ($parentModel->pivot->foo) получить не только поля таблицы, но и созданные в pivot-модели акцессоры(accessors). При работе только с pivot-таблицей обычно мы сначала создаем отношение «многие ко многим» и добавляем функцию withPivot(), в параметрах которой перечисляем поля из pivot-таблицы, иначе у нас будет доступ только к ключам этой таблицы, но если нам вдруг понадобится какая-то преобработка этих полей или какие-нибудь акцессоры, то для этого необходимо создать pivot-модель, объявить в ней акцессоры, а затем там где мы создавали отношение «многие ко многим» вызвать метод using() в параметрах которого передать название класса pivot-модели (PivotModel::class), после этого акцессоры станут доступными.

Более подробно написано тут: https://github.com/laravel/docs/commit/19679d47f9688facaea77ca1875d6c7577def6d9

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.