Работа с сущностями в базах данных

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

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

По своей сути, класс Entity — это просто класс, представляющий одну строку базы данных. Он имеет свойства класса для представления столбцов базы данных и предоставляет любые дополнительные методы для реализации бизнес-логики для этой строки. Однако основная особенность состоит в том, что он ничего не знает о том, как сохранить себя. Это ответственность модели или класса репозитория. Таким образом, если что-то изменится в том, как вам нужно сохранить объект, вам не нужно будет менять способ использования этого объекта во всем приложении. Это позволяет использовать файлы JSON или XML для хранения объектов на этапе быстрого прототипирования, а затем легко переключаться на базу данных, когда вы доказываете, что концепция работает.

Давайте рассмотрим очень простую пользовательскую сущность и то, как мы будем работать с ней, чтобы прояснить ситуацию.

Предположим, у вас есть таблица базы данных с именем users, имеющим следующую схему:

id          - integer
username    - string
email       - string
password    - string
created_at  - datetime

Создайте класс сущности

Теперь создайте новый класс Entity. Поскольку по умолчанию нет места для хранения этих классов и оно не вписывается в существующую структуру каталогов, создайте новый каталог в app / Entities . Создайте сам объект в app / Entities / User.php .

<?php namespace App\Entities;

use CodeIgniter\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';
    protected $useTimestamps = true;
}

Модель использует usersтаблицу в базе данных для всех своих действий. Мы установили $allowedFieldsсвойство для включения всех полей, которые мы хотим изменить вне классов. В idcreated_atи updated_atполя обрабатываются автоматически классом или в базе данных, поэтому мы не хотим , чтобы изменить их. Наконец, мы установили наш класс Entity как $returnType. Это гарантирует, что все методы модели, которые возвращают строки из базы данных, будут возвращать экземпляры нашего класса User Entity вместо объекта или массива, как обычно.

Работа с классом сущности

Теперь, когда все элементы на месте, вы можете работать с классом Entity, как с любым другим классом:

$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 не установил никаких свойств для столбцов, но вы все равно можете получить к ним доступ, как если бы они были общедоступными свойствами. Базовый класс CodeIgniterEntity позаботится об этом за вас, а также предоставит возможность проверять свойства с помощью isset () или unset () свойства и отслеживать, какие столбцы были изменены с момента создания или извлечения объекта. из базы данных.

Когда User передается методу модели save () , он автоматически считывает свойства и сохраняет любые изменения в столбцах, перечисленных в свойстве модели $ allowedFields . Он также знает, создавать ли новую строку или обновлять существующую.

Быстрое заполнение свойств

Класс Entity также предоставляет метод, fill()который позволяет вам вставить в класс массив пар ключ / значение и заполнить свойства класса. Любое свойство в массиве будет установлено для Entity. Однако при сохранении через модель только поля в $ 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);

Обработка бизнес-логики

Хотя приведенные выше примеры удобны, они не помогают реализовать какую-либо бизнес-логику. Базовый класс Entity реализует некоторые интеллектуальные методы __get()и __set()методы, которые будут проверять наличие специальных методов и использовать их вместо использования атрибутов напрямую, что позволяет принудительно применять любую бизнес-логику или преобразование данных, которые вам нужны.

Вот обновленная сущность User, чтобы предоставить несколько примеров того, как это можно использовать:

<?php namespace App\Entities;

use CodeIgniter\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()он преобразует время в форматированную строку в текущем часовом поясе приложения.

Хотя эти примеры довольно просты, они показывают, что использование классов Entity может обеспечить очень гибкий способ применения бизнес-логики и создания объектов, которые приятно использовать.

// Auto-hash the password - both do the same thing
$user->password = 'my great password';
$user->setPassword('my great password');

Отображение данных

На многих этапах своей карьеры вы будете сталкиваться с ситуациями, когда использование приложения изменилось, а исходные имена столбцов в базе данных больше не имеют смысла. Или вы обнаружите, что ваш стиль кодирования предпочитает свойства класса camelCase, но ваша схема базы данных требует имен snake_case. Эти ситуации могут быть легко решены с помощью функций сопоставления данных класса Entity.

В качестве примера представьте, что у вас есть упрощенная пользовательская сущность, которая используется во всем приложении:

<?php namespace App\Entities;

use CodeIgniter\Entity;

class User extends Entity
{
    protected $attributes = [
        'id' => null,
        'name' => null,        // Represents a username
        'email' => null,
        'password' => null,
        'created_at' => null,
        'updated_at' => null,
    ];
}

Ваш босс приходит к вам и говорит, что никто больше не использует имена пользователей, поэтому вы переходите на использование электронной почты только для входа в систему. Но они действительно хотят немного персонализировать приложение, поэтому они хотят, чтобы вы изменили поле имени, чтобы оно представляло полное имя пользователя, а не его имя пользователя, как это делается сейчас. Чтобы все было в порядке и чтобы все по-прежнему было осмысленным, вы запускаете миграцию, переименовав поле имени в full_name для ясности.

Игнорируя, насколько надуманным является этот пример, теперь у нас есть два варианта исправления класса User. Мы могли бы изменить свойство класса с $nameна $full_name, но это потребовало бы изменений во всем приложении. Вместо этого мы можем просто сопоставить full_nameстолбец в базе данных со $nameсвойством и закончить с изменениями Entity:

<?php namespace App\Entities;

use CodeIgniter\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 = [
        'full_name' => 'name'
    ],
}

Добавляя наше новое имя базы данных в $datamapмассив, мы можем сообщить классу, через какое свойство класса должен быть доступен столбец базы данных. Ключ массива — это имя столбца в базе данных, где значение в массиве — это свойство класса, с которым оно отображается.

В этом примере, когда модель устанавливает full_nameполе в классе User, она фактически присваивает это значение $nameсвойству класса , чтобы его можно было установить и получить через него $user->name. Значение по-прежнему будет доступно через оригинал $user->full_name, так как это необходимо для модели, чтобы вернуть данные и сохранить их в базе данных. Тем не менее, unsetи issetтолько работа на отображенной собственности, а $nameне на исходное имя, full_name.

Мутаторы

Мутаторы свидания

По умолчанию класс Entity будет преобразовывать поля с именами created_at , updated_at или deleted_at в экземпляры Time всякий раз, когда они устанавливаются или извлекаются. Класс Time предоставляет большое количество полезных методов неизменным и локализованным способом.

Вы можете определить, какие свойства будут автоматически преобразованы, добавив имя в массив options [‘ates ‘] :

<?php namespace App\Entities;

use CodeIgniter\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();

Кастинг недвижимости

Вы можете указать, что свойства в вашей Entity должны быть преобразованы в общие типы данных с помощью свойства cast . Этот параметр должен быть массивом, где ключ — это имя свойства класса, а значение — это тип данных, к которому он должен быть приведен. Приведение влияет только на считывание значений. Не происходит преобразований, влияющих на постоянное значение ни в сущности, ни в базе данных. Свойства могут быть приведены к любому из следующих типов данных: целое число , число с плавающей запятой , двойное , строковое , логическое , объект , массив , дата-время и отметка времени.. Добавьте вопросительный знак в начале типа, чтобы пометить свойство как допускающее значение NULL, т. Е. « Строка» , « целое число» .

Например, если у вас был объект User со свойством is_banned , вы можете преобразовать его как логическое значение:

<?php namespace App\Entities;

use CodeIgniter\Entity;

class User extends Entity
{
    protected $casts = [
        'is_banned' => 'boolean',
        'is_banned_nullable' => '?boolean'
    ],
}

Массив / Json Casting

Приведение в массив / Json особенно полезно с полями, в которых хранятся сериализованные массивы или json. При использовании как:

  • массив , они автоматически будут сериализации,
  • JSON , они будут автоматически установлены в качестве значения json_decode ($ значение, False),
  • JSON-массив , то они будут автоматически установлены в качестве значения json_decode ($ значения, правда),

когда вы читаете стоимость собственности. В отличие от остальных типов данных, в которые вы можете приводить свойства, следующие:

  • тип приведения массива будет сериализован,
  • json и json-array cast будут использовать функцию json_encode на

значение всякий раз, когда свойство установлено:

<?php namespace App\Entities;

use CodeIgniter\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);

Проверка измененных атрибутов

Вы можете проверить, изменился ли атрибут Entity с момента его создания. Единственный параметр — это имя проверяемого атрибута:

$user = new User();
$user->hasChanged('name');      // false

$user->name = 'Fred';
$user->hasChanged('name');      // true

Или, чтобы проверить всю сущность на наличие измененных значений, опустите параметр:

$user->hasChanged();            // true

Добавить комментарий