Модель Активной Записи
- Введение
- Определение модели
- Стандартные свойства
- Получение нескольких моделей
- Получение одиночных моделей / агрегатные функции
- Агрегатные функции
- Вставка и изменение моделей
- Простые вставки
- Простые изменения
- Массовое заполнение
- Удаление моделей
- Ограничения запросов
- События
- Основы использования
- Расширение моделей
# Введение
В OctoberCMS Вы можете использовать красивую и простую реализацию шаблона Active Record для работы с базой данных, которая основана на Eloquent ORM (opens new window). Каждая таблица имеет соответствующий класс-модель, который используется для работы с этой таблицей. Модели позволяют запрашивать данные из таблиц, а также вставлять в них новые записи.
Класс модели находится в папке плагина в подпапке models. Пример:
plugins/
acme/
blog/
models/
user/ <=== Model config directory
columns.yaml <=== Model config files
fields.yaml <==^
User.php <=== Model class
Plugin.php
Папка с настройками модели может содержать файлы с описанием столбцов списка и полей формы. Название этой папки совпадает с названием класса модели и должно быть написано строчными буквами.
# Определение модели
В большинстве случаев, для каждой таблице в базе должен существовать класс модели, который должен расширять класс Model
. Пример:
namespace Acme\Blog\Models;
use Model;
class Post extends Model
{
/**
* Название таблицы.
*
* @var string
*/
protected $table = 'acme_blog_posts';
}
Свойство $table
указывает на таблицу, соответствующей данной модели. Название таблицы состоит из имени автора, названия плагина и произвольного названия (которое должно быть осмысленным).
# Стандартные свойства
Вы можете использовать следующие стандартные свойства в модели:
class User extends Model
{
protected $primaryKey = 'id';
public $exists = false;
protected $dates = ['last_seen_at'];
public $timestamps = true;
protected $jsonable = ['permissions'];
protected $guarded = ['*'];
}
Свойство | Описание |
---|---|
$primaryKey | первичный ключ с именем id. |
$exists | указывает на то, что модель существует. |
$dates | указанные атрибуты преобразуются в экземпляр объекта Carbon/DateTime после получения. |
$timestamps | автоматически устанавливает поля created_at и updated_at. |
$jsonable | указанные атрибуты кодируются в JSON перед сохранением и преобразуются в массивы после получения. |
$fillable | определяет, для каких атрибутов модели разрешено массовое назначение. |
$guarded | определяет, для каких атрибутов модели запрещено массовое назначение. |
$visible | определяет атрибуты, которые можно показать в преобразованном массиве модели. |
$hidden | скрывает атрибуты из преобразованного массива модели — например, пароль у модели User . |
# Первичные ключи
Модель предполагает, что каждая таблица имеет первичный ключ с именем id
. Вы можете определить свойство $primaryKey
для указания другого имени. Пример:
class Post extends Model
{
/**
* Первичный ключ модели.
*
* @var string
*/
protected $primaryKey = 'id';
}
# Отметки времени
По умолчанию модель ожидает наличия в ваших таблицах столбцов updated_at
и created_at
. Если вы не хотите, чтобы они автоматически обрабатывались, установите свойство $timestamps
класса модели как false
:
class Post extends Model
{
/**
* Определяет необходимость отметок времени для модели.
*
* @var bool
*/
public $timestamps = false;
}
Если вы хотите изменить формат отметок времени, задайте свойство $dateFormat
вашей модели. Это свойство определяет, как атрибуты времени будут храниться в базе данных, а также задаёт их формат при сериализации модели в массив или JSON:
class Post extends Model
{
/**
* @var string
*/
protected $dateFormat = 'U';
}
# JSON атрибуты
Значения указанных в свойстве $jsonable
атрибутов кодируются в JSON перед сохранением и преобразуются в массивы после получения из базы данных:
class Post extends Model
{
/**
* @var array
*/
protected $jsonable = ['data'];
}
# Получение нескольких моделей
После создания модели и связанной с ней таблицы, вы можете начать получать значения из вашей базы данных. Каждая модель представляет собой мощный конструктор запросов, позволяющий удобно выполнять запросы к связанной таблице. Например:
$flights = Flight::all();
# Доступ к значениям столбцов
Если у вас есть экземпляр модели, Вы можете обращаться к значениям столбцов модели, обращаясь к соответствующим свойствам. Например, давайте пройдёмся по каждому экземпляру Flight, возвращённому нашим запросом, и выведем значение столбца name
:
foreach ($flights as $flight) {
echo $flight->name;
}
# Добавление дополнительных ограничений
Метод all
возвращает все результаты из таблицы модели. Поскольку каждая модель работает как конструктор запросов, Вы можете также добавить ограничения в запрос, а затем использовать метод get
для получения результатов:
$flights = Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
Примечание: Все методы, доступные в конструкторе запросов, также доступны при работе с моделями. Вы можете использовать любой из них.
# Коллекции
Такие методы, как all
и get
, которые получают несколько результатов, возвращают экземпляр Collection
. Этот класс предоставляет большое количество полезных методов для работы с результатами запроса. Само собой, Вы можете просто перебирать такую коллекцию в цикле как массив:
foreach ($flights as $flight) {
echo $flight->name;
}
# Разделение результата на блоки
Если вам нужно обработать тысячи записей, используйте команду chunk
(блок — прим. пер.). Метод chunk
получает модель частями, передавая их в Closure
для обработки. Использование этого метода уменьшает используемый объём оперативной памяти:
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
Первый передаваемый в метод аргумент — число записей, получаемых в одном "блоке". Передаваемая в качестве второго аргумента функция будет вызываться для каждого блока, получаемого из БД.
# Получение одиночных моделей / агрегатные функции
Кроме получения всех записей указанной таблицы Вы можете также получить конкретные записи при помощи методов find
и first
. Вместо коллекции моделей эти методы возвращают один экземпляр модели:
// Retrieve a model by its primary key
$flight = Flight::find(1);
// Retrieve the first model matching the query constraints
$flight = Flight::where('active', 1)->first();
# Исключения «Не найдено»
Иногда вам нужно возбудить исключение, если определённая модель не была найдена. Например в маршрутах или контроллерах. Методы findOrFail
и firstOrFail
получают первый результат запроса. А если результатов нет, то происходит исключение Illuminate\Database\Eloquent\ModelNotFoundException:
$model = Flight::findOrFail(1);
$model = Flight::where('legs', '>', 100)->firstOrFail();
Если исключение не поймано, пользователю автоматически посылается HTTP-отклик 404, поэтому нет необходимости писать явные проверки для возврата откликов 404 при использовании этих методов:
Route::get('/api/flights/{id}', function ($id) {
return Flight::findOrFail($id);
});
# Агрегатные функции
Вы также можете использовать агрегатные функции конструктора запросов, такие как count
, max
, sum
и другие. Эти методы возвращают соответствующее скалярное значение вместо полного экземпляра модели:
$count = Flight::where('active', 1)->count();
$max = Flight::where('active', 1)->max('price');
# Вставка и изменение моделей
# Простые вставки
Для создания новой записи в БД просто создайте экземпляр модели, задайте атрибуты модели и вызовите метод save
:
$flight = new Flight;
$flight->name = 'Sydney to Canberra';
$flight->save();
В этом примере мы просто создали экземпляр модели Flight
и присвоили значение параметру name
. При вызове метода save
запись будет вставлена в таблицу. Отметки времени created_at
и updated_at
будут автоматически установлены, поэтому их не надо указывать вручную.
# Простые изменения
Метод save
можно использовать и для изменения существующей модели в БД. Для изменения модели сначала Вам нужно получить её, далее изменить необходимые атрибуты и вызвать метод save
. Отметка времени updated_at
будет установлена автоматически, поэтому её не надо задавать вручную:
$flight = Flight::find(1);
$flight->name = 'Darwin to Adelaide';
$flight->save();
Изменения можно выполнить для нескольких моделей, которые соответствуют указанному запросу. В этом примере все рейсы, которые отмечены как active
и имеют destination
равное San Diego
, будут отмечены как delayed:
Flight::where('is_active', true)
->where('destination', 'Perth')
->update(['delayed' => true]);
Метод update
ожидает массив пар столбец/значение, обозначающий, какие столбцы нужно изменить.
# Массовое заполнение
Вы также можете использовать метод create
для создания и сохранения модели одной строкой. Метод вернёт добавленную модель. Однако перед этим вам нужно определить либо свойство fillable
, либо guarded
в классе модели, так как изначально все модели защищены от массового заполнения.
Уязвимость массового заполнения проявляется, когда пользователь передаёт с помощью запроса неподходящий HTTP-параметр, и вы не ожидаете, что этот параметр изменит столбец в вашей БД. Например, злоумышленник может послать в HTTP-запросе параметр is_admin
, который затем применяется к методу create
вашей модели, позволяя пользователю повысить свои привилегии до администратора.
Поэтому, для начала нужно определить, для каких атрибутов разрешить массовое заполнение. Это делается с помощью свойства модели $fillable
. Например, давайте разрешим массовое назначение атрибута name
нашей модели Flight
:
class Flight extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
}
Теперь мы можем использовать метод create
для вставки новой записи в базу данных. Метод create
возвращает сохранённый экземпляр модели:
$flight = Flight::create(['name' => 'Flight 10']);
Параметр $fillable
служит «белым списком» атрибутов, для которых разрешено массовое назначение. А параметр $guarded
служит «чёрным списком». Параметр $guarded
должен содержать массив атрибутов, для которых будет запрещено массовое назначение. Атрибутам, не вошедшим в этот массив, будет разрешено массовое назначение. Само собой, вы должны использовать только один из этих параметров:
class Flight extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = ['price'];
}
В этом примере всем атрибутам кроме price
разрешено массовое назначение.
Вы также можете запретить массовое заполнение всем атрибутам, используя символ *
.
# Другие методы создания
Возможно, когда-нибудь Вам понадобится создать модель без ее сохранения. Для этого можно использовать метод make
.
$flight = Flight::make(['name' => 'Flight 10']);
// Functionally the same as...
$flight = new Flight;
$flight->fill(['name' => 'Flight 10']);
Существует ещё два метода, которые можно использовать для создания моделей с помощью массового заполнения: firstOrCreate
и firstOrNew
. Метод firstOrCreate
пытается найти запись БД, используя указанные пары столбец/значение. Если модель не найдена в БД, запись будет вставлена в БД с указанными атрибутами.
Метод firstOrNew
как и firstOrCreate
пытается найти в БД запись, соответствующую указанным атрибутам. Однако если модель не найдена, будет возвращён новый экземпляр модели. Учтите, что эта модель ещё не помещена в БД. Вам надо вызвать метод save
вручную, чтобы сохранить её:
// Получить рейс по атрибутам или создать его, если он не существует
$flight = Flight::firstOrCreate(['name' => 'Flight 10']);
// Получить рейс по атрибутам, или создать новый экземпляр
$flight = Flight::firstOrNew(['name' => 'Flight 10']);
# Удаление моделей
Используйте метод delete
, чтобы удалить модель:
$flight = Flight::find(1);
$flight->delete();
# Удаление модели по ключу
В предыдущем примере мы получили модель из БД перед вызовом метода delete
. Но если вы знаете первичный ключ модели, вы можете удалить модель, не получая её. Для этого вызовите метод destroy
:
Flight::destroy(1);
Flight::destroy([1, 2, 3]);
Flight::destroy(1, 2, 3);
# Удаление модели запросом
Вы также можете выполнить запрос на удаление на наборе моделей. В следующем примере мы удалим все неактивные рейсы:
$deletedRows = Flight::where('active', 0)->delete();
Примечание: Важно отметить, что при таком способе удаления события не сработают.
# Ограничения запросов
Ограничения позволяют Вам определить набор условий, который Вы можете использовать в приложении. Например, если Вам часто требуется получать пользователей, которые сейчас «популярны». Для создания заготовки просто начните имя метода с префикса scope
:
class User extends Model
{
/**
* Scope a query to only include popular users.
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* Scope a query to only include active users.
*/
public function scopeActive($query)
{
return $query->where('is_active', 1);
}
}
# Использование ограничений запросов
Когда Вы определили ограничения, то можете вызывать нужный метод при запросах к модели. Но теперь Вам не нужно использовать префикс scope
, например:
$users = User::popular()->active()->orderBy('created_at')->get();
Вы даже можете сцеплять вызовы разных ограничений.
# Динамические ограничения
Иногда Вам может потребоваться определить ограничения, которые принимают параметры. Для этого, просто, добавьте нужные параметры в метод после аргумента $query
:
class User extends Model
{
/**
* Пример запроса пользователей определённого типа.
*/
public function scopeApplyType($query, $type)
{
return $query->where('type', $type);
}
}
А затем передайте их при вызове метода:
$users = User::applyType('admin')->get();
# События
События позволяют вам легко выполнять код при каждом сохранении, удалении или изменении класса конкретной модели в базе данных. Доступны следующие методы:
Event | Description |
---|---|
beforeCreate | перед сохранением модели (при создании). |
afterCreate | после сохранения модели (при создании). |
beforeSave | перед сохранением модели (при создании или обновлении). |
afterSave | после сохранения модели (при создании или обновлении). |
beforeValidate | перед валидацией модели. |
afterValidate | после валидации модели. |
beforeUpdate | перед обновлением модели. |
afterUpdate | после обновления модели. |
beforeDelete | перед удалением модели. |
afterDelete | после удаления модели. |
beforeRestore | перед восстановлением модели. |
afterRestore | после восстановления модели. |
beforeFetch | перед заполнением модели. |
afterFetch | после заполнения модели. |
Пример:
/**
* Генерируем URL
*/
public function beforeCreate()
{
$this->slug = Str::slug($this->name);
}
# Основы использования
Когда новая модель сохраняется впервые, возникают события beforeCreate
и afterCreate
. Если модель уже существовала на момент вызова метода save
, вызываются события beforeUpdate
/ afterUpdate
. В обоих случаях также возникнут события beforeSave
/ afterSave
.
Например, давайте определим слушателя событий, заполняющий атрибут slug
при создании модели:
/**
* Генерируем URL
*/
public function beforeCreate()
{
$this->slug = Str::slug($this->name);
}
Если обработчики creating
, updating
, saving
или deleting
вернут значение false, то действие будет отменено:
public function beforeCreate()
{
if (!$user->isValid()) {
return false;
}
}
Вы можете использовать метод bindEvent
, чтобы связать локальные события с экземпляром модели. Название метода должно быть таким же, как и название переопределяемого метода с префиксом model.
.
$flight = new Flight;
$flight->bindEvent('model.beforeCreate', function() use ($model) {
$model->slug = Str::slug($model->name);
})
# Расширение моделей
Так как модели имеют поведение, то они могут быть расширены при помощи метода extend()
. Пример:
User::extend(function($model) {
$model->hasOne['author'] = ['Author', 'key' => 'user_id'];
});
User::extend(function($model) {
$model->bindEvent('model.beforeSave', function() use ($model) {
// ...
});
});