Документация Laravel 10.x
Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.
Аксессоры, мутаторы и приведение атрибутов позволяют вам преобразовывать значения атрибутов Eloquent при их получении или установке на экземпляры модели. Например, вы можете использовать шифровщик Laravel, чтобы шифровать значение во время его хранения в базе данных, а затем автоматически дешифровать атрибут при доступе к нему через модель Eloquent. Или вы можете преобразовать JSON-строку, хранящуюся в вашей базе данных, в массив при ее доступе через вашу модель Eloquent.
Аксессор преобразует значение атрибута Eloquent при его доступе. Чтобы определить аксессор, создайте защищенный метод в вашей модели для представления доступного атрибута. Имя этого метода должно соответствовать "camel case" представлению истинного базового атрибута модели / столбца базы данных, когда это применимо.
В этом примере мы определим аксессор для атрибута first_name
. Аксессор будет автоматически вызван Eloquent при попытке получения значения атрибута first_name
. Все методы аксессоров / мутаторов атрибутов должны объявлять тип возврата Illuminate\Database\Eloquent\Casts\Attribute
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Получить имя пользователя. */ protected function firstName(): Attribute { return Attribute::make( get: fn (string $value) => ucfirst($value), ); }}
Все методы аксессоров возвращают экземпляр Attribute
, который определяет, как будет осуществлен доступ к атрибуту и, при необходимости, его изменение. В этом примере мы определяем только, как будет осуществлен доступ к атрибуту. Для этого мы предоставляем аргумент get
конструктору класса Attribute
.
Как видите, исходное значение колонки передается аксессору, позволяя вам манипулировать и возвращать значение. Чтобы получить доступ к значению аксессора, вы можете просто обратиться к атрибуту first_name
на экземпляре модели:
use App\Models\User; $user = User::find(1); $firstName = $user->first_name;
Примечание Если вы хотите, чтобы эти вычисляемые значения были добавлены к представлениям вашей модели в массиве / JSON, вам нужно добавить их.
Иногда ваш аксессор может потребовать преобразования нескольких атрибутов модели в единый "объект значения". Для этого ваше замыкание get
может принимать второй аргумент $attributes
, который будет автоматически предоставлен замыканию и будет содержать массив всех текущих атрибутов модели:
use App\Support\Address;use Illuminate\Database\Eloquent\Casts\Attribute; /** * Взаимодействует с адресом пользователя. */protected function address(): Attribute{ return Attribute::make( get: fn (mixed $value, array $attributes) => new Address( $attributes['address_line_one'], $attributes['address_line_two'], ), );}
При возвращении объектов значений из аксессоров любые изменения, внесенные в объект значения, автоматически синхронизируются с моделью перед сохранением модели. Это возможно, потому что Eloquent сохраняет экземпляры, возвращаемые аксессорами, чтобы он мог возвращать тот же экземпляр при каждом вызове аксессора:
use App\Models\User; $user = User::find(1); $user->address->lineOne = 'Updated Address Line 1 Value';$user->address->lineTwo = 'Updated Address Line 2 Value'; $user->save();
Однако иногда вы можете захотеть включить кэширование для примитивных значений, таких как строки и булевы значения, особенно если они требуют вычислительных затрат. Для этого вы можете вызвать метод shouldCache
при определении вашего аксессора:
protected function hash(): Attribute{ return Attribute::make( get: fn (string $value) => bcrypt(gzuncompress($value)), )->shouldCache();}
Если вы хотите отключить кэширование объекта атрибутов, вы можете вызвать метод withoutObjectCaching
при определении атрибута:
/** * Взаимодействует с адресом пользователя. */protected function address(): Attribute{ return Attribute::make( get: fn (mixed $value, array $attributes) => new Address( $attributes['address_line_one'], $attributes['address_line_two'], ), )->withoutObjectCaching();}
Мутатор преобразует значение атрибута Eloquent при его установке. Чтобы определить мутатор, вы можете предоставить аргумент set
при определении вашего атрибута. Давайте определим мутатор для атрибута first_name
. Этот мутатор будет автоматически вызван, когда мы попытаемся установить значение атрибута first_name
в модели:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Взаимодействует с именем пользователя. */ protected function firstName(): Attribute { return Attribute::make( get: fn (string $value) => ucfirst($value), set: fn (string $value) => strtolower($value), ); }}
Замыкание мутатора получит значение, которое устанавливается в атрибут, что позволяет вам манипулировать значением и возвращать измененное значение. Чтобы использовать наш мутатор, нам нужно всего лишь установить атрибут first_name
в модели Eloquent:
use App\Models\User; $user = User::find(1); $user->first_name = 'Sally';
В этом примере коллбэк set
будет вызван с значением Sally
. Затем мутатор применит функцию strtolower
к имени и установит его измененное значение во внутренний массив $attributes
модели.
Иногда вашему мутатору может потребоваться установить несколько атрибутов в базовой модели. Для этого вы можете вернуть массив из замыкания set
. Каждый ключ в массиве должен соответствовать базовому атрибуту / столбцу базы данных, связанному с моделью:
use App\Support\Address;use Illuminate\Database\Eloquent\Casts\Attribute; /** * Взаимодействует с адресом пользователя. */protected function address(): Attribute{ return Attribute::make( get: fn (mixed $value, array $attributes) => new Address( $attributes['address_line_one'], $attributes['address_line_two'], ), set: fn (Address $value) => [ 'address_line_one' => $value->lineOne, 'address_line_two' => $value->lineTwo, ], );}
Приведение атрибута предоставляет функциональность, аналогичную аксессорам и мутаторам, не требуя при этом определения дополнительных методов в вашей модели. Вместо этого свойство $casts
вашей модели предоставляет удобный метод преобразования атрибутов в общие типы данных.
Свойство $casts
должно быть массивом, где ключ - это имя приводимого атрибута, а значение - это тип, в который вы хотите привести столбец. Поддерживаемые типы приведения:
array
AsStringable::class
boolean
collection
date
datetime
immutable_date
immutable_datetime
decimal:<precision>
double
encrypted
encrypted:array
encrypted:collection
encrypted:object
float
hashed
integer
object
real
string
timestamp
Для демонстрации приведения атрибута давайте приведем атрибут is_admin
, который хранится в нашей базе данных как целое число (0
или 1
), к значению boolean:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Атрибуты, которые должны быть приведены. * * @var array */ protected $casts = [ 'is_admin' => 'boolean', ];}
После определения каста, атрибут is_admin
будет всегда приведен к boolean при его доступе, даже если базовое значение хранится в базе данных как целое число:
$user = App\Models\User::find(1); if ($user->is_admin) { // ...}
Если вам нужно добавить новый временный каст во время выполнения, вы можете использовать метод mergeCasts
. Эти определения кастов будут добавлены к любым уже определенным на модели кастам:
$user->mergeCasts([ 'is_admin' => 'integer', 'options' => 'object',]);
Внимание Атрибуты со значением
null
не будут приведены. Кроме того, никогда не стоит определять каст (или атрибут) с тем же именем, что и у отношения, или присваивать каст первичному ключу модели.
Вы можете использовать класс приведения Illuminate\Database\Eloquent\Casts\AsStringable
, чтобы привести атрибут модели к объекту строки Illuminate\Support\Stringable:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\AsStringable;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Атрибуты, которые должны быть приведены. * * @var array */ protected $casts = [ 'directory' => AsStringable::class, ];}
Приведение к типу array
особенно полезно при работе с колонками, хранящимися в виде сериализованного JSON. Например, если ваша база данных имеет тип поля JSON
или TEXT
, содержащего сериализованный JSON, добавление приведения к типу array
к этому атрибуту автоматически десериализует атрибут в PHP-массив при его доступе в вашей модели Eloquent:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Атрибуты, которые должны быть приведены. * * @var array */ protected $casts = [ 'options' => 'array', ];}
После определения каста вы можете обращаться к атрибуту options
, и он будет автоматически десериализован из JSON в PHP-массив. Когда вы устанавливаете значение атрибута options
, данный массив автоматически сериализуется обратно в JSON для хранения:
use App\Models\User; $user = User::find(1); $options = $user->options; $options['key'] = 'value'; $user->options = $options; $user->save();
Чтобы обновить одно поле атрибута JSON с более кратким синтаксисом, вы можете использовать оператор ->
при вызове метода update
:
$user = User::find(1); $user->update(['options->key' => 'value']);
Хотя стандартного каста array
достаточно для многих приложений, у него есть некоторые недостатки. Поскольку каст array
возвращает примитивный тип, невозможно прямо изменить смещение массива. Например, следующий код вызовет ошибку PHP:
$user = User::find(1); $user->options['key'] = $value;
Для решения этой проблемы Laravel предлагает каст AsArrayObject
, который приводит ваш атрибут JSON к классу ArrayObject. Эта функция реализована с использованием механизма пользовательских кастов Laravel, что позволяет Laravel интеллектуально кэшировать и трансформировать мутированный объект так, чтобы можно было изменять отдельные смещения без вызова ошибки PHP. Чтобы использовать каст AsArrayObject
, просто назначьте его атрибуту:
use Illuminate\Database\Eloquent\Casts\AsArrayObject; /** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'options' => AsArrayObject::class,];
Точно так же Laravel предлагает каст AsCollection
, который приводит ваш атрибут JSON к экземпляру Laravel Collection:
use Illuminate\Database\Eloquent\Casts\AsCollection; /** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'options' => AsCollection::class,];
Если вы хотите, чтобы каст AsCollection
создавал экземпляр пользовательского класса коллекции вместо базового класса коллекции Laravel, вы можете предоставить имя класса коллекции в качестве аргумента каста:
use App\Collections\OptionCollection;use Illuminate\Database\Eloquent\Casts\AsCollection; /** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'options' => AsCollection::class.':'.OptionCollection::class,];
По умолчанию Eloquent будет приводить столбцы created_at
и updated_at
к экземплярам Carbon, который расширяет класс PHP DateTime
и предоставляет ассортимент полезных методов. Вы можете приводить дополнительные атрибуты дат, определяя дополнительные касты дат в массиве свойства $casts
вашей модели. Обычно даты следует приводить с использованием типов datetime
или immutable_datetime
.
При определении каста date
или datetime
вы также можете указать формат даты. Этот формат будет использоваться, когда модель сериализуется в массив или JSON:
/** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'created_at' => 'datetime:Y-m-d',];
Когда столбец приводится к типу дата, вы можете установить значение соответствующего атрибута модели в метку времени UNIX, строку даты (Y-m-d
), строку даты и времени или экземпляр DateTime
/ Carbon
. Значение даты будет правильно преобразовано и сохранено в вашей базе данных.
Вы можете настроить формат сериализации по умолчанию для всех дат вашей модели, определив метод serializeDate
в вашей модели. Этот метод не влияет на форматирование ваших дат для хранения в базе данных:
/** * Подготавливает дату для сериализации в массив / JSON. */protected function serializeDate(DateTimeInterface $date): string{ return $date->format('Y-m-d');}
Чтобы указать формат, который следует использовать при хранении фактических дат модели в вашей базе данных, вы должны определить свойство $dateFormat
в вашей модели:
/** * Формат хранения столбцов даты модели. * * @var string */protected $dateFormat = 'U';
По умолчанию касты date
и datetime
сериализуют даты в UTC строку даты в формате ISO-8601 (YYYY-MM-DDTHH:MM:SS.uuuuuuZ
), независимо от часового пояса, указанного в опции конфигурации часового пояса вашего приложения. Вам настоятельно рекомендуется всегда использовать этот формат сериализации, а также хранить даты вашего приложения в часовом поясе UTC, не изменяя опцию конфигурации часового пояса вашего приложения с ее значения по умолчанию UTC
. Постоянное использование часового пояса UTC в вашем приложении обеспечит максимальный уровень совместимости с другими библиотеками для работы с датами на PHP и JavaScript.
Если применен пользовательский формат к касту date
или datetime
, например, datetime:Y-m-d H:i:s
, во время сериализации даты будет использоваться внутренний часовой пояс экземпляра Carbon. Обычно это будет часовой пояс, указанный в опции конфигурации часового пояса вашего приложения.
Eloquent также позволяет приводить значения ваших атрибутов к перечислениям PHP. Для этого вы можете указать атрибут и перечисление, которое вы хотите привести, в массиве свойства $casts
вашей модели:
use App\Enums\ServerStatus; /** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'status' => ServerStatus::class,];
После определения каста в вашей модели указанный атрибут будет автоматически приведен к перечислению и обратно при взаимодействии с атрибутом:
if ($server->status == ServerStatus::Provisioned) { $server->status = ServerStatus::Ready; $server->save();}
Иногда вам может понадобиться, чтобы ваша модель хранила массив значений перечисления в одной колонке. Для этого вы можете использовать касты AsEnumArrayObject
или AsEnumCollection
, предоставленные Laravel:
use App\Enums\ServerStatus;use Illuminate\Database\Eloquent\Casts\AsEnumCollection; /** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'statuses' => AsEnumCollection::class.':'.ServerStatus::class,];
Каст encrypted
будет шифровать значение атрибута модели с использованием встроенных средств шифрования Laravel. Кроме того, касты encrypted:array
, encrypted:collection
, encrypted:object
, AsEncryptedArrayObject
и AsEncryptedCollection
работают так же, как и их незашифрованные аналоги; однако, как вы могли бы ожидать, базовое значение шифруется при сохранении в вашей базе данных.
Поскольку окончательная длина зашифрованного текста непредсказуема и длиннее, чем соответствующий текст в открытом виде, убедитесь, что связанная колонка базы данных имеет тип TEXT
или больше. Кроме того, поскольку значения шифруются в базе данных, вы не сможете выполнять запросы или поиск значений зашифрованных атрибутов.
Как вы можете знать, Laravel шифрует строки, используя значение конфигурации key
, указанное в файле конфигурации app
вашего приложения. Обычно это значение соответствует значению переменной окружения APP_KEY
. Если вам нужно изменить ключ шифрования вашего приложения, вам придется вручную повторно зашифровать ваши зашифрованные атрибуты с использованием нового ключа.
Иногда вам может потребоваться применять касты при выполнении запроса, например, при выборе сырого значения из таблицы. Например, рассмотрим следующий запрос:
use App\Models\Post;use App\Models\User; $users = User::select([ 'users.*', 'last_posted_at' => Post::selectRaw('MAX(created_at)') ->whereColumn('user_id', 'users.id')])->get();
Атрибут last_posted_at
в результатах этого запроса будет простой строкой. Было бы замечательно, если бы мы могли применить каст datetime
к этому атрибуту при выполнении запроса. К счастью, мы можем добиться этого, используя метод withCasts
:
$users = User::select([ 'users.*', 'last_posted_at' => Post::selectRaw('MAX(created_at)') ->whereColumn('user_id', 'users.id')])->withCasts([ 'last_posted_at' => 'datetime'])->get();
В Laravel есть различные встроенные и полезные типы кастов; однако иногда вам может потребоваться определить свои собственные типы кастов. Чтобы создать каст, выполните команду Artisan make:cast
. Новый класс каста будет размещен в вашем каталоге app/Casts
:
php artisan make:cast Json
Все классы пользовательских кастов реализуют интерфейс CastsAttributes
. Классы, реализующие этот интерфейс, должны определить методы get
и set
. Метод get
отвечает за преобразование сырого значения из базы данных в значение каста, в то время как метод set
должен преобразовывать значение каста в сырое значение, которое можно сохранить в базе данных. В качестве примера мы переопределим встроенный тип каста json
в виде пользовательского типа каста:
<?php namespace App\Casts; use Illuminate\Contracts\Database\Eloquent\CastsAttributes;use Illuminate\Database\Eloquent\Model; class Json implements CastsAttributes{ /** * Привести заданное значение. * * @param array<string, mixed> $attributes * @return array<string, mixed> */ public function get(Model $model, string $key, mixed $value, array $attributes): array { return json_decode($value, true); } /** * Подготовить заданное значение для хранения. * * @param array<string, mixed> $attributes */ public function set(Model $model, string $key, mixed $value, array $attributes): string { return json_encode($value); }}
После определения пользовательского типа каста вы можете прикрепить его к атрибуту модели, используя его имя класса:
<?php namespace App\Models; use App\Casts\Json;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Атрибуты, которые должны быть приведены. * * @var array */ protected $casts = [ 'options' => Json::class, ];}
Вы не ограничены приведением значений к примитивным типам. Вы также можете приводить значения к объектам. Определение пользовательских кастов, которые приводят значения к объектам, очень похоже на приведение к примитивным типам; однако метод set
должен возвращать массив пар ключ / значение, который будет использоваться для установки сырых, хранимых значений в модели.
В качестве примера определим пользовательский класс каста, который приводит несколько значений модели в один объект значения Address
. Мы предположим, что значение Address
имеет два открытых свойства: lineOne
и lineTwo
:
<?php namespace App\Casts; use App\ValueObjects\Address as AddressValueObject;use Illuminate\Contracts\Database\Eloquent\CastsAttributes;use Illuminate\Database\Eloquent\Model;use InvalidArgumentException; class Address implements CastsAttributes{ /** * Привести заданное значение. * * @param array<string, mixed> $attributes */ public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject { return new AddressValueObject( $attributes['address_line_one'], $attributes['address_line_two'] ); } /** * Подготовить заданное значение для хранения. * * @param array<string, mixed> $attributes * @return array<string, string> */ public function set(Model $model, string $key, mixed $value, array $attributes): array { if (! $value instanceof AddressValueObject) { throw new InvalidArgumentException('The given value is not an Address instance.'); } return [ 'address_line_one' => $value->lineOne, 'address_line_two' => $value->lineTwo, ]; }}
При приведении к объектам значений все изменения, внесенные в объект значения, автоматически синхронизируются с моделью перед сохранением модели:
use App\Models\User; $user = User::find(1); $user->address->lineOne = 'Updated Address Value'; $user->save();
Примечание Если вы планируете сериализовать ваши Eloquent-модели, содержащие объекты значений, в JSON или массивы, вы должны реализовать интерфейсы
Illuminate\Contracts\Support\Arrayable
иJsonSerializable
в объекте значения.
При разрешении атрибутов, которые приведены к объектам значений, Eloquent кэширует их. Поэтому, если к атрибуту снова обратиться, будет возвращен тот же экземпляр объекта.
Если вы хотите отключить поведение кэширования объекта пользовательских классов кастов, вы можете объявить публичное свойство withoutObjectCaching
в своем пользовательском классе каста:
class Address implements CastsAttributes{ public bool $withoutObjectCaching = true; // ...}
При преобразовании модели Eloquent в массив или JSON с использованием методов toArray
и toJson
ваши объекты значений пользовательских кастов обычно также будут сериализованы, если они реализуют интерфейсы Illuminate\Contracts\Support\Arrayable
и JsonSerializable
. Однако при использовании объектов значений, предоставленных библиотеками сторонних разработчиков, у вас может не быть возможности добавить эти интерфейсы к объекту.
Поэтому вы можете указать, что ваш пользовательский класс каста будет отвечать за сериализацию объекта значения. Для этого ваш пользовательский класс каста должен реализовать интерфейс Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes
. В этом интерфейсе указано, что ваш класс должен содержать метод serialize
, который должен возвращать сериализованную форму вашего объекта значения:
/** * Получить сериализованное представление значения. * * @param array<string, mixed> $attributes */public function serialize(Model $model, string $key, mixed $value, array $attributes): string{ return (string) $value;}
Временами вам может потребоваться написать пользовательский класс каста, который преобразует только те значения, которые устанавливаются в модели, и не выполняет никаких операций при получении атрибутов из модели.
В кастах, применяемых только входящим образом, должен реализовываться интерфейс CastsInboundAttributes
, который требует только определения метода set
. Команду Artisan make:cast
можно вызвать с опцией --inbound
, чтобы сгенерировать класс каста, применяемого только входящим образом:
php artisan make:cast Hash --inbound
Классическим примером каста, применяемого только входящим образом, является "хеширование". Например, мы можем определить каст, который хеширует входящие значения с использованием заданного алгоритма:
<?php namespace App\Casts; use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;use Illuminate\Database\Eloquent\Model; class Hash implements CastsInboundAttributes{ /** * Создать новый экземпляр класса каста. */ public function __construct( protected string|null $algorithm = null, ) {} /** * Подготовить заданное значение для хранения. * * @param array<string, mixed> $attributes */ public function set(Model $model, string $key, mixed $value, array $attributes): string { return is_null($this->algorithm) ? bcrypt($value) : hash($this->algorithm, $value); }}
При присоединении пользовательского класса каста к модели можно указать параметры каста, разделяя их от имени класса с использованием символа :
и разделяя несколько параметров запятой. Параметры будут переданы в конструктор класса каста:
/** * Атрибуты, которые должны быть приведены. * * @var array */protected $casts = [ 'secret' => Hash::class.':sha256',];
Вы можете разрешить вашим объектам значения приложения определять свои собственные классы пользовательских кастов. Вместо присоединения пользовательского класса каста к вашей модели вы можете альтернативно присоединить класс объекта значения, который реализует интерфейс Illuminate\Contracts\Database\Eloquent\Castable
:
use App\Models\Address; protected $casts = [ 'address' => Address::class,];
Объекты, реализующие интерфейс Castable
, должны определять метод castUsing
, который возвращает имя класса пользовательского кастера, ответственного за приведение класса каста и из него, в класс Castable
:
<?php namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Castable;use App\Casts\Address as AddressCast; class Address implements Castable{ /** * Получить имя класса кастера для использования при кастинге в/из этой целевой модели. * * @param array<string, mixed> $arguments */ public static function castUsing(array $arguments): string { return AddressCast::class; }}
При использовании классов Castable
вы все равно можете предоставлять аргументы в определении $casts
. Аргументы будут переданы методу castUsing
:
use App\Models\Address; protected $casts = [ 'address' => Address::class.':argument',];
Комбинируя "касты" с анонимными классами PHP, вы можете определить объект значения и его логику приведения в одном объекте каста. Для этого верните анонимный класс из метода castUsing
вашего объекта значения. Анонимный класс должен реализовывать интерфейс CastsAttributes
:
<?php namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Castable;use Illuminate\Contracts\Database\Eloquent\CastsAttributes;use Illuminate\Database\Eloquent\Model; class Address implements Castable{ // ... /** * Получить класс кастера для использования при кастинге в/из этой целевой модели. * * @param array<string, mixed> $arguments */ public static function castUsing(array $arguments): CastsAttributes { return new class implements CastsAttributes { public function get(Model $model, string $key, mixed $value, array $attributes): Address { return new Address( $attributes['address_line_one'], $attributes['address_line_two'] ); } public function set(Model $model, string $key, mixed $value, array $attributes): array { return [ 'address_line_one' => $value->lineOne, 'address_line_two' => $value->lineTwo, ]; } }; }}