Spec-Zone.ru › CodeIgniter 4

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

CodeIgniter поддерживает классы сущностей как полноправных участников в его уровне базы данных, при этом их использование остается полностью необязательным. Они обычно используются в рамках паттерна репозитория, но могут быть использованы напрямую с моделью, если это лучше соответствует вашим потребностям.

  • Использование сущностей
    • Создание класса сущности
    • Создание модели
    • Работа с классом сущности
    • Быстрое заполнение свойств
    • Массовый доступ к свойствам
  • Обработка бизнес-логики
  • Картирование данных
  • Мутаторы
    • Мутаторы дат
    • Приведение типов свойств
    • Приведение типов массива/JSON
    • Приведение типов CSV
    • Пользовательское приведение типов
  • Проверка на изменения атрибутов

Использование сущностей

В основе класса сущности лежит простой класс, представляющий собой единственную строку базы данных. Он имеет свойства класса для представления столбцов базы данных и предоставляет любые дополнительные методы для реализации бизнес-логики для этой строки. Однако основная функция заключается в том, что он ничего не знает о том, как сохранять себя. Это ответственность модели или класса репозитория. Таким образом, если что-то изменится в том, как вам нужно сохранить объект, вам не нужно изменять способ использования этого объекта во всем приложении. Это позволяет использовать файлы 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API