git 6ac13f37adbed3ce6a6532fd790f70bd731b8571
Аксессоры, мутаторы и приведение атрибутов к типам позволяют преобразовывать значения атрибутов Eloquent, когда вы извлекаете экземпляр модели или присваиваете их экземпляру модели. Например, вы можете использовать шифровальщик Laravel, чтобы зашифровать значение при его сохранении в базу данных, а затем автоматически расшифровать атрибут при доступе к нему в модели Eloquent. Или вы можете преобразовать строку JSON, которая хранится в вашей базе данных, в массив при доступе к ней через вашу модель Eloquent.
Аксессор преобразует значение атрибута экземпляра Eloquent при обращении к нему. Чтобы определить метод доступа, создайте метод get{Attribute}Attribute
в вашей модели, где {Attribute}
– это имя столбца, к которому вы хотите получить доступ, в «верхнем» регистре.
В этом примере мы определим аксессор для атрибута first_name
. Аксессор будет автоматически вызван Eloquent при попытке получить значение атрибута first_name
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Получить имя пользователя.
*
* @param string $value
* @return string
*/
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
}
Как видите, исходное значение столбца передается аксессору, что позволяет вам манипулировать и возвращать значение. Чтобы получить доступ к значению аксессора, вы можете просто получить доступ к атрибуту first_name
экземпляра модели:
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;
Вы не ограничены взаимодействием с одним атрибутом в вашем аксессоре. Вы также можете использовать аксессор для возврата новых вычисленных значений из существующих атрибутов:
/**
* Получить полное имя пользователя.
*
* @return string
*/
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
{tip} Если вы хотите, чтобы эти вычисленные значения были добавлены к представлениям массива / JSON вашей модели, вам нужно будет добавить их.
Мутатор преобразует значение атрибута в момент их присвоения экземпляру Eloquent. Чтобы определить мутатор, определите метод set{Attribute}Attribute
в вашей модели, где {Attribute}
– это имя столбца, к которому вы хотите получить доступ, в «верхнем» регистре.
Определим мутатор для атрибута first_name
. Этот мутатор будет автоматически вызываться, когда мы попытаемся присвоить значение атрибута first_name
модели:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Присвоить имя пользователю.
*
* @param string $value
* @return void
*/
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}
Мутатор получит значение, заданное для атрибута, что позволит вам манипулировать этим значением и устанавливать желаемое значение во внутреннем свойстве $attributes
модели Eloquent. Чтобы использовать наш мутатор, нам нужно только установить атрибут first_name
для модели Eloquent:
use App\Models\User;
$user = User::find(1);
$user->first_name = 'Sally';
В этом примере метод setFirstNameAttribute
будет вызываться со значением Sally
. Затем, мутатор применит к имени функцию strtolower
и установит полученное значение во внутреннем массиве $attributes
.
Приведение атрибутов обеспечивает функциональность, аналогичную аксессорам и мутаторам, но без необходимости определения каких-либо дополнительных методов вашей модели. Вместо этого свойство $casts
вашей модели представляет удобный способ преобразования атрибутов в распространенные типы данных.
Свойство $casts
должно быть массивом, где ключ – это имя преобразуемого атрибута, а значение – это тип, к которому вы хотите привести столбец. Поддерживаемые типы преобразования:
array
AsStringable::class
boolean
collection
date
datetime
immutable_date
immutable_datetime
decimal:<digits>
double
encrypted
encrypted:array
encrypted:collection
encrypted:object
float
integer
object
real
string
timestamp
Чтобы продемонстрировать преобразование атрибутов, давайте преобразуем атрибут is_admin
, который хранится в нашей базе данных в виде целого числа (0
или 1
), в логическое значение:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Атрибуты, которые должны быть типизированы.
*
* @var array
*/
protected $casts = [
'is_admin' => 'boolean',
];
}
После определения типизации, атрибут is_admin
всегда будет преобразован в логическое значение при доступе к нему, даже если базовое значение хранится в базе данных как целое число:
$user = App\Models\User::find(1);
if ($user->is_admin) {
//
}
Если вам нужно добавить новое временное приведение во время выполнения, вы можете использовать метод mergeCasts
. Эти определения приведения будут добавлены к любому из уже определенных для модели приведения:
$user->mergeCasts([
'is_admin' => 'integer',
'options' => 'object',
]);
{note} Атрибуты, которые имеют значение
null
, не будут преобразованы. Кроме того, вы никогда не должны определять типизацию (или атрибут), имя которого совпадает с именем отношения.
Вы можете использовать класс приведения Illuminate\Database\Eloquent\Casts\AsStringable
для приведения атрибута модели к объекту строки Fluent 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
этому атрибуту автоматически десериализует атрибут модели Eloquent в массив PHP при обращении к нему:
<?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,
];
По умолчанию Eloquent преобразует столбцы created_at
и updated_at
в экземпляры Carbon, расширяющего класс DateTime PHP и предоставляющего набор полезных методов. Вы можете типизировать дополнительные атрибуты даты, определив дополнительные преобразования даты в массиве свойств вашей модели $cast
. Обычно даты следует приводить с использованием типизации datetime
или immutable_datetime
.
При определении типизации date
или datetime
вы также можете указать формат даты. Этот формат будет использоваться, когда модель сериализуется в массив или JSON:
/**
* Атрибуты, которые должны быть типизированы.
*
* @var array
*/
protected $casts = [
'created_at' => 'datetime:Y-m-d',
];
Когда столбец типизирован как дата, вы можете установить соответствующее значение атрибута модели в виде временной метки форматов UNIX, строки даты (Y-m-d
), строки даты-времени или экземпляров DateTime
/ Carbon
. Значение даты будет правильно преобразовано и сохранено в вашей базе данных.
Вы можете настроить формат сериализации по умолчанию для всех дат вашей модели, переопределив метод serializeDate
вашей модели. Этот метод не влияет на форматирование дат для их сохранения в базе данных:
/**
* Подготовить дату для сериализации массива / JSON.
*
* @param \DateTimeInterface $date
* @return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d');
}
Чтобы указать формат, который следует использовать при фактическом сохранении дат модели в вашей базе данных, вы должны определить свойство $dateFormat
вашей модели:
/**
* Формат хранения столбцов даты модели.
*
* @var string
*/
protected $dateFormat = 'U';
По умолчанию приведение date
и datetime
будут сериализовывать даты в строку даты UTC ISO-8601 (1986-05-28T21:05:54.000000Z
), независимо от часового пояса, указанного в конфигурации timezone
вашего приложения. Вам настоятельно рекомендуется всегда использовать этот формат сериализации, а также хранить даты вашего приложения в часовом поясе UTC, не изменяя параметр конфигурации вашего приложения timezone
от значения по умолчанию UTC
. Последовательное использование часового пояса UTC во всем приложении обеспечит максимальный уровень взаимодействия с другими библиотеками обработки даты, написанными на PHP и JavaScript.
Если к приведению date
или datetime
применяется настраиваемый формат, такой как datetime:Y-m-d H:i:s
, внутренний часовой пояс экземпляра Carbon будет использоваться во время сериализации даты. Обычно это часовой пояс, указанный в параметре конфигурации вашего приложения timezone
.
{note} Приведение Enum доступно только для PHP 8.1+.
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();
}
Приведение encrypted
зашифрует значение атрибута модели, используя встроенные в Laravel функции шифрования. Кроме того, преобразование encrypted:array
, encrypted:collection
, encrypted:object
, AsEncryptedArrayObject
и AsEncryptedCollection
работает так же, как и их незашифрованные копии; однако, как и следовало ожидать, базовое значение зашифровано при хранении в вашей базе данных.
Поскольку окончательная длина зашифрованного текста непредсказуема и больше, чем его копия в виде обычного текста, убедитесь, что связанный столбец базы данных имеет тип TEXT
или больше. Кроме того, поскольку значения зашифрованы в базе данных, вы не сможете запрашивать или искать зашифрованные значения атрибутов.
Иногда может потребоваться применить типизацию при выполнении запроса, например, при выборе сырого значения из таблицы. Например, рассмотрим следующий запрос:
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 есть множество встроенных полезных преобразователей; однако иногда требуется определить свои собственные. Вы можете добиться этого, определив класс, реализующий интерфейс CastsAttributes
.
Классы, реализующие этот интерфейс, должны определять методы get
и set
. Метод get
отвечает за преобразование сырого значения из базы данных к типизированному значению, а метод set
– должен преобразовывать типизированное значение в сырое значение, которое можно сохранить в базе данных. В качестве примера мы повторно реализуем встроенный преобразователь json
как пользовательский типизатор:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Json implements CastsAttributes
{
/**
* Преобразовать значение к пользовательскому типу.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array
*/
public function get($model, $key, $value, $attributes)
{
return json_decode($value, true);
}
/**
* Подготовить переданное значение к сохранению.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
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\Models\Address as AddressModel;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use InvalidArgumentException;
class Address implements CastsAttributes
{
/**
* Преобразовать значение к пользовательскому типу.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return \App\Models\Address
*/
public function get($model, $key, $value, $attributes)
{
return new AddressModel(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
/**
* Подготовить переданное значение к сохранению.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param \App\Models\Address $value
* @param array $attributes
* @return array
*/
public function set($model, $key, $value, $attributes)
{
if (! $value instanceof AddressModel) {
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();
{tip} Если вы планируете сериализовать свои модели Eloquent, содержащие объекты-значения, в JSON или массивы, вам следует реализовать интерфейсы
Illuminate\Contracts\Support\Arrayable
иJsonSerializable
для объекта-значения.
Когда модель Eloquent преобразуется в массив или JSON с использованием методов toArray
и toJson
, ваши пользовательские типизаторы объекты-значения обычно будут сериализованы, в частности, пока они (типизаторы) реализуют интерфейсы Illuminate\Contracts\Support\Arrayable
и JsonSerializable
. Однако при использовании объектов-значений, предоставляемых сторонними библиотеками, у вас может не быть возможности добавить эти интерфейсы к объекту.
Поэтому вы можете указать, что ваш собственный класс типизатора будет отвечать за сериализацию объекта-значения. Для этого ваш собственный класс типизатора должен реализовывать интерфейс Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes
. В этом интерфейсе указано, что ваш класс должен содержать метод serialize
, возвращающий сериализованную форму вашего объекта значения:
/**
* Получить сериализованное представление значения.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*/
public function serialize($model, string $key, $value, array $attributes)
{
return (string) $value;
}
Иногда требуется написать свой типизатор, который только преобразует указанные значения атрибутов модели, и не выполняет никаких операций при обращении к этим атрибутам. Классическим примером только входящей типизации является «хеширование». Пользовательские типизаторы только для входящих значений должны реализовывать интерфейс CastsInboundAttributes
, требующий определение метода set
.
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
class Hash implements CastsInboundAttributes
{
/**
* Алгоритм хеширования.
*
* @var string
*/
protected $algorithm;
/**
* Создать новый экземпляр класса типизации.
*
* @param string|null $algorithm
* @return void
*/
public function __construct($algorithm = null)
{
$this->algorithm = $algorithm;
}
/**
* Подготовить переданное значение к сохранению.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
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
, который возвращает имя пользовательского класса типизатора, отвечающего за двустороннее преобразование:
<?php
namespace App\Models;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\Address as AddressCast;
class Address implements Castable
{
/**
* Получить имя класса типизатора для использования двустороннего преобразования.
*
* @param array $arguments
* @return string
*/
public static function castUsing(array $arguments)
{
return AddressCast::class;
}
}
При использовании классов Castable
вы все равно можете указывать аргументы в свойстве $casts
. Аргументы будут переданы методу castUsing
:
use App\Models\Address;
protected $casts = [
'address' => Address::class.':argument',
];
Комбинируя castable
и анонимными классами PHP, вы можете определить объект-значение и его логику преобразования как единый типизируемый объект. Для этого верните анонимный класс из метода castUsing
вашего объекта-значения. Анонимный класс должен реализовывать интерфейс CastsAttributes
:
<?php
namespace App\Models;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Address implements Castable
{
// ...
/**
* Получить имя класса типизатора для использования двустороннего преобразования.
*
* @param array $arguments
* @return object|string
*/
public static function castUsing(array $arguments)
{
return new class implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return new Address(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
public function set($model, $key, $value, $attributes)
{
return [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
];
}
};
}
}