Использование классов сущностей
CodeIgniter поддерживает классы сущностей как полноправных участников в его уровне базы данных, при этом их использование остается полностью необязательным. Они обычно используются в рамках паттерна репозитория, но могут быть использованы напрямую с моделью, если это лучше соответствует вашим потребностям.
- Использование сущностей
- Обработка бизнес-логики
- Картирование данных
- Мутаторы
- Проверка на изменения атрибутов
Использование сущностей
В основе класса сущности лежит простой класс, представляющий собой единственную строку базы данных. Он имеет свойства класса для представления столбцов базы данных и предоставляет любые дополнительные методы для реализации бизнес-логики для этой строки. Однако основная функция заключается в том, что он ничего не знает о том, как сохранять себя. Это ответственность модели или класса репозитория. Таким образом, если что-то изменится в том, как вам нужно сохранить объект, вам не нужно изменять способ использования этого объекта во всем приложении. Это позволяет использовать файлы JSON или XML для хранения объектов на стадии быстрого прототипирования, а затем легко переключиться на базу данных, когда вы подтвердили работоспособность концепции.
Давайте пройдёмся по очень простому классу сущности пользователя и тому, как мы с ним будем работать, чтобы прояснить ситуацию.
Предположим, у вас есть таблица базы данных с именем users, имеющая следующую схему:
id - integer username - string email - string password - string created_at - datetime
Создание класса сущности
Теперь создайте новый класс сущности. Поскольку нет стандартного расположения для хранения этих классов, и это не соответствует существующей структуре каталогов, создайте новый каталог в app/Entities. Создайте саму сущность в app/Entities/User.php.
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
// ...
}
В самом простом виде, этого достаточно, хотя мы сделаем его более полезным через некоторое время.
Создание модели
Сначала создайте модель в app/Models/UserModel.php, чтобы мы могли с ней взаимодействовать:
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $table = 'users';
protected $allowedFields = [
'username', 'email', 'password',
];
protected $returnType = \App\Entities\User::class;
protected $useTimestamps = true;
}
Модель использует таблицу users в базе данных для всех своих операций. Мы установили свойство $allowedFields, чтобы включить все поля, которые мы хотим изменять вне класса. Поля id, created_at и updated_at обрабатываются автоматически классом или базой данных, поэтому мы не хотим их изменять. Наконец, мы установили класс сущности как $returnType. Это гарантирует, что все методы модели, возвращающие строки из базы данных, будут возвращать экземпляры нашего класса сущности User вместо объекта или массива, как обычно.
Работа с классом сущности
Теперь, когда все части готовы, вы будете работать с классом сущности, как с любым другим классом:
$user = $userModel->find($id);
// Display
echo $user->username;
echo $user->email;
// Updating
unset($user->username);
if (! isset($user->username) {
$user->username = 'something new';
}
$userModel->save($user);
// Create
$user = new \App\Entities\User();
$user->username = 'foo';
$user->email = 'foo@example.com';
$userModel->save($user);
Вы могли заметить, что класс User не задал никаких свойств для столбцов, но вы всё равно можете получить к ним доступ, как если бы они были публичными свойствами. Базовый класс CodeIgniter\Entity позаботится об этом для вас, а также предоставит возможность проверки свойств с помощью isset() или unset() свойств и отслеживания того, какие столбцы изменились с момента создания объекта или извлечения из базы данных.
Когда User передаётся в метод save() модели, он автоматически заботится о чтении свойств и сохранении любых изменений в столбцах, перечисленных в свойстве модели $allowedFields. Он также знает, следует ли создавать новую строку или обновлять существующую.
Примечание
Когда мы вызываем метод insert(), все значения из сущности передаются в метод, но когда мы вызываем метод update(), передаются только изменённые значения.
Быстрое заполнение свойств
Класс сущности также предоставляет метод fill(), который позволяет поместить массив пар ключ/значение в класс и заполнить свойства класса. Любое свойство в массиве будет установлено в сущности. Однако при сохранении через модель будут сохранены только поля в $allowedFields, поэтому вы можете хранить дополнительные данные в сущностях, не беспокоясь о том, что лишние поля будут неправильно сохранены.
$data = $this->request->getPost(); $user = new \App\Entities\User(); $user->fill($data); $userModel->save($user);
Вы также можете передать данные в конструктор, и данные будут переданы через метод fill() во время создания экземпляра.
$data = $this->request->getPost(); $user = new \App\Entities\User($data); $userModel->save($user);
Массовый доступ к свойствам
Класс сущности имеет два метода для извлечения всех доступных свойств в массив: toArray() и toRawArray(). Использование необработанного варианта позволит обойти магические методы «получателя» и преобразования. Оба метода могут принимать первый булевый параметр для указания, следует ли фильтровать возвращаемые значения по изменённым, и заключительный булевый параметр для рекурсивного выполнения метода в случае вложенных сущностей.
Обработка бизнес-логики
Хотя приведенные выше примеры удобны, они не помогают обеспечить соблюдение бизнес-логики. Базовый класс сущности реализует некоторые умные методы __get() и __set(), которые проверяют специальные методы и используют их вместо непосредственного использования атрибутов, что позволяет реализовать необходимую бизнес-логику или преобразование данных.
Вот обновлённая сущность пользователя, чтобы продемонстрировать примеры использования:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
use CodeIgniter\I18n\Time;
class User extends Entity
{
public function setPassword(string $pass)
{
$this->attributes['password'] = password_hash($pass, PASSWORD_BCRYPT);
return $this;
}
public function setCreatedAt(string $dateString)
{
$this->attributes['created_at'] = new Time($dateString, 'UTC');
return $this;
}
public function getCreatedAt(string $format = 'Y-m-d H:i:s')
{
// Convert to CodeIgniter\I18n\Time object
$this->attributes['created_at'] = $this->mutateDate($this->attributes['created_at']);
$timezone = $this->timezone ?? app_timezone();
$this->attributes['created_at']->setTimezone($timezone);
return $this->attributes['created_at']->format($format);
}
}
Первое, что нужно заметить, это имена добавленных методов. Для каждого из них класс ожидает, что имя столбца в формате snake_case будет преобразовано в PascalCase и префиксённо либо set, либо get. Эти методы затем будут автоматически вызываться всякий раз, когда вы устанавливаете или получаете свойство класса с помощью прямого синтаксиса (например, $user->email). Методы не обязательно должны быть публичными, если вы не хотите, чтобы к ним был доступ из других классов. Например, свойство класса created_at будет доступно через методы setCreatedAt() и getCreatedAt().
Примечание
Это работает только при попытке доступа к свойствам извне класса. Любые внутренние методы класса должны вызывать методы setX() и getX() напрямую.
В методе setPassword() мы гарантируем, что пароль всегда хешируется.
В методе setCreatedAt() мы преобразуем строку, полученную от модели, в объект DateTime, гарантируя, что наш часовой пояс UTC, чтобы мы могли легко преобразовать текущий часовой пояс зрителя. В методе getCreatedAt() он преобразует время в отформатированную строку в текущем часовом поясе приложения.
Хотя примеры довольно простые, они показывают, что использование классов сущностей может обеспечить очень гибкий способ обеспечения соблюдения бизнес-логики и создания объектов, приятных в использовании.
// Auto-hash the password - both do the same thing
$user->password = 'my great password';
$user->setPassword('my great password');
Картирование данных
Во многих ситуациях в вашей работе вы столкнётесь с тем, что использование приложения изменилось, и исходные имена столбцов в базе данных больше не имеют смысла. Или вы обнаружите, что ваш стиль программирования предпочитает camelCase для свойств класса, но ваша схема базы данных требовала snake_case. Эти ситуации легко решаются с помощью функций сопоставления данных класса сущности.
Например, представьте себе упрощённую сущность пользователя, которая используется во всём приложении:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
protected $attributes = [
'id' => null,
'name' => null, // Represents a username
'email' => null,
'password' => null,
'created_at' => null,
'updated_at' => null,
];
}
Ваш руководитель подходит к вам и говорит, что никто больше не использует имена пользователей, поэтому вы переходите к использованию только адресов электронной почты для входа. Но они хотят немного персонализировать приложение, поэтому они хотят, чтобы поле имени представляло полное имя пользователя, а не его имя пользователя, как это происходит сейчас. Чтобы поддерживать порядок и обеспечить сохранение смысла в базе данных, вы создаёте миграцию для переименования поля name в full_name для ясности.
Не обращая внимания на то, насколько этот пример искусственный, у нас есть два варианта решения проблемы класса User. Мы можем изменить свойство класса от $name до $full_name, но это потребует изменений во всём приложении. Вместо этого мы можем просто сопоставить столбец full_name в базе данных со свойством класса $name и закончить изменения в сущности:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
protected $attributes = [
'id' => null,
'name' => null, // Represents a username
'email' => null,
'password' => null,
'created_at' => null,
'updated_at' => null,
];
protected $datamap = [
'name' => 'full_name',
];
}
Добавив новое имя базы данных в массив $datamap, мы можем указать классу, через какое свойство класса должен быть доступен столбец базы данных. Ключом массива является свойство класса, а значением - имя столбца в базе данных.
В этом примере, когда модель устанавливает поле full_name в классе User, она фактически присваивает это значение свойству класса $name, поэтому оно может быть установлено и получено через $user->name. Значение всё ещё будет доступно через исходное $user->full_name, так как это необходимо для получения данных модели и сохранения их в базе данных. Однако unset и isset работают только со свойством сопоставления, $name, а не с оригинальным именем full_name.
Мутаторы
Мутаторы дат
По умолчанию класс сущности преобразует поля, имеющие имена created_at, updated_at или deleted_at, в экземпляры Time при их установке или получении. Класс Time предоставляет большое количество полезных методов в неизменяемом, локализованном формате.
Вы можете определить, какие свойства автоматически преобразуются, добавив их имя в массив options[‘dates’]:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
}
Теперь, когда любое из этих свойств будет установлено, оно будет преобразовано в экземпляр Time, используя текущий часовой пояс приложения, как задано в app/Config/App.php:
$user = new \App\Entities\User();
// Converted to Time instance
$user->created_at = 'April 15, 2017 10:30:00';
// Can now use any Time methods:
echo $user->created_at->humanize();
echo $user->created_at->setTimezone('Europe/London')->toDateString();
Преобразование свойств
Вы можете указать, что свойства в вашем сущности должны быть преобразованы в общие типы данных с помощью свойства casts. Этот параметр должен быть массивом, где ключ — имя свойства класса, а значение — тип данных, к которому оно должно быть преобразовано. Преобразование влияет только на чтение значений. Преобразования, которые влияют на постоянное значение в сущности или базе данных, не выполняются. Свойства можно преобразовать в следующие типы данных: integer, float, double, string, boolean, object, array, datetime, timestamp и uri. Добавьте вопросительный знак в начале типа, чтобы пометить свойство как допускающее значение null, например, ?string, ?integer.
Например, если у вас есть сущность пользователя с свойством is_banned, вы можете преобразовать его в булево значение:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
protected $casts = [
'is_banned' => 'boolean',
'is_banned_nullable' => '?boolean',
];
}
Преобразование массивов/JSON
Преобразование массивов/JSON особенно полезно для полей, хранящих сериализованные массивы или JSON. При преобразовании в:
- массив, они будут автоматически десериализованы,
- json, они будут автоматически установлены в качестве значения json_decode($value, false),
- json-массив, они будут автоматически установлены в качестве значения json_decode($value, true),
при установке значения свойства. В отличие от других типов данных, в которые можно преобразовывать свойства,:
- тип преобразования массив будет сериализовать,
- типы преобразования json и json-массив будут использовать функцию json_encode для
значения всякий раз, когда свойство устанавливается:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
protected $casts = [
'options' => 'array',
'options_object' => 'json',
'options_array' => 'json-array',
];
}
$user = $userModel->find(15); $options = $user->options; $options['foo'] = 'bar'; $user->options = $options; $userModel->save($user);
Преобразование CSV
Если вы знаете, что у вас есть плоский массив простых значений, кодирование их в сериализованную или JSON строку может быть сложнее, чем исходная структура. Преобразование в значения, разделенные запятыми (CSV), — это более простой способ, который приведет к строке, занимающей меньше места и более легко читаемой человеком:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class Widget extends Entity
{
protected $casts = [
'colors' => 'csv',
];
}
Хранится в базе данных как «red,yellow,green»:
$widget->colors = ['red', 'yellow', 'green'];
Примечание
Преобразование в CSV использует внутренние методы implode и explode PHP и предполагает, что все значения являются строковыми, безопасными и не содержат запятых. Для более сложных преобразований данных используйте array или json.
Настраиваемое преобразование
Вы можете определить собственные типы преобразований для получения и установки данных.
Сначала вам нужно создать обработчик класса для вашего типа. Предположим, что класс будет расположен в каталоге «app/Entity/Cast»:
<?php
namespace App\Entity\Cast;
use CodeIgniter\Entity\Cast\BaseCast;
//The class must inherit the CodeIgniter\Entity\Cast\BaseCast class
class CastBase64 extends BaseCast
{
public static function get($value, array $params = [])
{
return base64_decode($value);
}
public static function set($value, array $params = [])
{
return base64_encode($value);
}
}
Теперь вам нужно зарегистрировать его:
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class MyEntity extends Entity
{
// Specifying the type for the field
protected $casts = [
'key' => 'base64',
];
//Bind the type to the handler
protected $castHandlers = [
'base64' => \App\Entity\Cast\CastBase64::class,
];
}
//...
$entity->key = 'test'; // dGVzdA==
echo $entity->key; // test
Если вам не нужно изменять значения при получении или установке значения. Тогда просто не реализовывайте соответствующий метод:
use CodeIgniter\Entity\Cast\BaseCast;
class CastBase64 extends BaseCast
{
public static function get($value, array $params = [])
{
return base64_decode($value);
}
}
Параметры
В некоторых случаях одного типа недостаточно. В этой ситуации вы можете использовать дополнительные параметры. Дополнительные параметры указываются в квадратных скобках и перечисляются через запятую.
type[param1, param2]
// Defining a type with parameters
protected $casts = [
'some_attribute' => 'class[App\SomeClass, param2, param3]',
];
// Bind the type to the handler
protected $castHandlers = [
'class' => 'SomeHandler',
];
use CodeIgniter\Entity\Cast\BaseCast;
class SomeHandler extends BaseCast
{
public static function get($value, array $params = [])
{
var_dump($params);
// array(3) {
// [0]=>
// string(13) "App\SomeClass"
// [1]=>
// string(6) "param2"
// [2]=>
// string(6) "param3"
// }
}
}
Примечание
Если тип преобразования помечен как допускающий значение null ?bool, а переданное значение не равно null, то параметр со значением nullable будет передан обработчику типа преобразования. Если тип преобразования имеет предопределенные параметры, то nullable будет добавлен в конец списка.
Проверка измененных атрибутов
Вы можете проверить, изменился ли атрибут сущности с момента его создания. Единственным параметром является имя атрибута, который нужно проверить:
$user = new \App\Entities\User();
$user->hasChanged('name'); // false
$user->name = 'Fred';
$user->hasChanged('name'); // true
Или для проверки всей сущности на наличие измененных значений опустите параметр:
$user->hasChanged(); // true
© 2014–2020 British Columbia Institute of Technology
Licensed under the MIT License.
https://codeigniter.com/user_guide/models/entities.html