Usage
- С чего начать?
- Схема
- Создание таблиц в базе данных
- Методы для поиска моделей
- Реализация сложных классов
-
Класс
Model
-
Класс
Connector
- Класс
PluginReference
Модель - это класс, который наследуется от \Leadvertex\Plugin\Components\Db\Model
и позволяет сохранять объект в базу.
Существует три возможных сценария использования:
- для любого класса (ниже пример
User
) - для классов плагина (ниже пример
Shipment
) - для классов плагина, которые имеют один экземпляр (ниже пример
Settings
)
Давайте напишем модели для каждого из сценариев.
В качестве примера, напишем класс User
, у которого, есть два поля: $name
и $age
:
class User extends \Leadvertex\Plugin\Components\Db\Model
{
public string $name;
public int $age;
public function getId()
{
return $this->id;
}
public function setId($uuid)
{
$this->id = $uuid;
}
public static function schema(): array
{
return [
'name' => ['VARCHAR(255)'],
'age' => ['INT'],
];
}
}
Класс должен наследоваться от Model
.
Как вы могли заметить, мы написали метод public static function schema(): array
.
Данный метод, возвращает схему, по которой будет создана таблица в базе.
В ней должны содержаться все поля класса, которые необходимо записывать в базу. Важное уточнение, поле, которое необходимо сохранять в базу не может быть private
. Подробнее о схеме в соответствующем разделе документации.
Так же, мы инициализируем поле $id
, которое объявлено в классе Model
.
Обратите внимание, что поле $id
, намерено не было добавлено в схему, так как оно добавится автоматически.
Рекомендуется, в $id
передавать сгенерированный uuid
, например, при помощи \Leadvertex\Plugin\Components\Db\Helpers\UuidHelper
, как в следующем примере записи. Но прежде чем записать модель в базу, должна быть создана таблица, при помощи консольной команды php console.php db:create-tables
подробнее в соответствующем разделе документации.
Пример записи модели User
в базу и чтения из нее:
$uuid = \Leadvertex\Plugin\Components\Db\Helpers\UuidHelper::getUuid();
$model = new User();
$model->setId($uuid);
$model->name = 'Sasha';
$model->age = 25;
$model->save();
$id = $model->getId();
$findModel = User::findById($id);
echo $findModel->name; // возвращает 'Sasha'
echo $findModel->age; // возвращает '25'
После чего, можно создать объект класса User
,$model = new User()
, и установив в поля, необходимые значения $model->name = 'Sasha'
и $model->age = 25
, сохранить, используя $model->save()
.
В базе появится запись:
id | name | age |
---|---|---|
0f8169f8-ffb6-4801-8791-8008476acbeb | Sasha | 25 |
Для того, что-бы найти модель, можно воспользоваться методами для поиска, объявленными в классе Model
. Давайте попробуем найти сохраненную модель:
$findModelById = User::findById(1);
echo $findModelById->name; // возвращает 'Sasha'
echo $findModelById->age; // возвращает '25'
echo $findModelById->getId(); // возвращает '1'
$findModelByIds = User::findByIds([1, 2, 3, 4]); // массив индексируется в соответствии с id
echo $findModelById[1]->name; // возвращает 'Sasha'
echo $findModelById[1]->age; // возвращает '25'
echo $findModelById[1]->getId(); // возвращает '1'
$findModelByCondition = User::findByCondition(['name' => 'Sasha']); // массив индексируется в соответствии с id
echo $findModelByCondition[1]->name; // возвращает 'Sasha'
echo $findModelByCondition[1]->age; // возвращает '25'
echo $findModelByCondition[1]->getId(); // возвращает '1'
Подробнее об этих методах и их особенностях в соответствующем разделе документации.
В качестве примера, напишем класс Shipment
, у которого, есть два поля: $address
и $postcode
:
class Shipment extends Model implements \Leadvertex\Plugin\Components\Db\PluginModelInterface
{
public string $address;
public string $postcode;
public function __construct($id, string $address, string $postcode)
{
$this->id = $id;
$this->postcode = $postcode;
$this->address = $address;
}
public function getAddress(): string
{
return $this->address;
}
public function getPostcode(): string
{
return $this->postcode;
}
public static function schema(): array
{
return [
'address' => ['VARCHAR(255)'],
'postcode' => ['VARCHAR(255)'],
];
}
}
Класс должен наследоваться от Model
и реализовывать интерфейс \Leadvertex\Plugin\Components\Db\PluginModelInterface
.
В Model
, для PluginModelInterface
создаются дополнительные поля, а именно:
companyId
pluginAlias
pluginId
Аналогично прошлому примеру, поля должны быть public
или protected
.
Обратите внимание, что в schema()
, мы не объявляем id
, companyId
, pluginAlias
и pluginId
. Они будут добавлены автоматически.
Пример записи модели Shipment
в базу и чтения из нее:
$companyId = 1;
$pluginAlias = 'user';
$pluginId = 1;
$reference = new \Leadvertex\Plugin\Components\Db\Components\PluginReference($companyId, $pluginAlias, $pluginId);
Connector::setReference($reference);
$shipmentModel = new Shipment(1, 'Moscow', '123456');
$shipmentModel->save();
$findModel = Shipment::findById(1);
echo $findModel->getAddress(); // возвращает 'Moscow'
echo $findModel->getPostcode(); // возвращает '123456'
Аналогично примеру с User
, инициализируем бд и создаем таблицу в ней, для нашей модели. Но теперь еще и устанавливаем ссылку на плагин.
Делаем это при помощи статичного метода setReference
в классе Connector
. Передаем в него объект \Leadvertex\Plugin\Components\Db\Components\PluginReference
, который в себе содержит три поля:
-
companyId
- Идентификатор компании, которая использует плагин -
pluginAlias
- Псевдоним плагина(от чьего имени исполняется) -
pluginId
- Идентификатор плагина
Сохранение и поиск, выполняются аналогично. Однако разница есть, а именно в содержимом базы:
companyId | pluginAlias | pluginId | id | address | postcode |
---|---|---|---|---|---|
1 | user | 1 | 1 | Moscow | 123456 |
В таблице видно, что в базе модель, помимо, добавленных нами address
и postcode
, содержит дополнительные столбцы, как уже упоминалось, они добавляются автоматически.
Давайте изменим ссылку на плагин:
Connector::setReference(new PluginReference(2, $pluginAlias, $pluginId));
После чего сохраним еще одну модель, с теми же значениями:
$newShipmentModel = new Shipment(1, 'Moscow', '123456');
$newShipmentModel->save();
После этого в базе появится запись, в которой отличается только companyId
, который мы изменили в setReference
:
companyId | pluginAlias | pluginId | id | address | postcode |
---|---|---|---|---|---|
1 | user | 1 | 1 | Moscow | 123456 |
2 | user | 1 | 1 | Moscow | 123456 |
Модели сохранялись с одинаковыми полями и $id, однако, в базе, companyId
у них разные.
Добавим еще одну модель:
$newShipmentModel = new Shipment(2, 'New York', '123456');
$newShipmentModel->save();
В базе:
companyId | pluginAlias | pluginId | id | address | postcode |
---|---|---|---|---|---|
1 | user | 1 | 1 | Moscow | 123456 |
2 | user | 1 | 1 | Moscow | 123456 |
2 | user | 1 | 2 | New York | 123456 |
Давайте попробуем найти модели:
$findModelById = Shipment::findById(1);
echo $findModelById->address; // возвращает 'Moscow'
echo $findModelById->postcode; // возвращает '123456'
echo $findModelById->getId(); // возвращает '1'
$findModelByIds = Shipment::findByIds([1, 2, 3, 4]); // массив индексируется в соответствии с id
echo $findModelById[1]->address; // возвращает 'Moscow'
echo $findModelById[1]->postcode; // возвращает '123456'
echo $findModelById[1]->getId(); // возвращает '1'
echo $findModelById[2]->address; // возвращает 'New York'
echo $findModelById[2]->postcode; // возвращает '123456'
echo $findModelById[2]->getId(); // возвращает '1'
$findModelByCondition = Post::findByCondition(['address' => 'New York']); // массив индексируется в соответствии с id
echo $findModelByCondition[2]->name; // возвращает 'New York'
echo $findModelByCondition[2]->age; // возвращает '123456'
echo $findModelByCondition[2]->getId(); // возвращает '2'
Сменим ссылку на плагин:
Connector::setReference(new PluginReference(1, $pluginAlias, $pluginId));
По пробуем теперь найти модели:
$findModelById = Shipment::findById(1);
echo $findModelById->address; // возвращает 'Moscow'
echo $findModelById->postcode; // возвращает '123456'
echo $findModelById->getId(); // возвращает '1'
$findModelByIds = Shipment::findByIds([1, 2, 3, 4]); // массив индексируется в соответствии с id
echo $findModelById[1]->address; // возвращает 'Moscow'
echo $findModelById[1]->postcode; // возвращает '123456'
echo $findModelById[1]->getId(); // возвращает '1'
$findModelByCondition = Post::findByCondition(['address' => 'New York']); // массив индексируется в соответствии с id
print_r($findModelByCondition); // возвращает 'Array()'
Как видите, находятся только те модели, для которой ссылка на плагин соответствует установленной в Connector::setReference()
.
Происходит так из-за того, что для PluginModelInterface
, в качестве уникального идентификатора служат поля
companyId + pluginAlias + pluginId + id
и при выполнении записи или чтения модели, они подставляются автоматически. Необходимо это, для того, что-бы автоматически предотвращать случайный доступ к чужим данным(другой компании или пользователя).
Что произойдет, если попробовать удалить найденную модель?
$findModelById = Shipment::findById(1);
$findModelById->delete();
В базе:
companyId | pluginAlias | pluginId | id | address | postcode |
---|---|---|---|---|---|
2 | user | 1 | 1 | Moscow | 123456 |
2 | user | 1 | 2 | New York | 123456 |
То есть удалилась модель с id = 1
, но только для компании с companyId = 1
, так как именно для нее установленна ссылка на плагин. Данные другой компании, не изменились.
В качестве примера, напишем класс Settings
, у которого, есть два поля: $deliveryType
и $packType
:
class Settings extends Model implements \Leadvertex\Plugin\Components\Db\SinglePluginModelInterface
{
public string $deliveryType;
public string $packType;
public static function schema(): array
{
return [
'deliveryType' => ['VARCHAR(255)'],
'packType' => ['VARCHAR(255)'],
];
}
}
Класс должен наследоваться от Model
и реализовывать интерфейс \Leadvertex\Plugin\Components\Db\SinglePluginModelInterface
].
Аналогично прошлым примерам, пишем метод schema()
. В данном примере не инициализируем поле $id
специально, так как объект данного класса может быть только один в базе.
Давайте попробуем записать модель Settings
в базу, после чего, найдем ее и изменим:
$companyId = 1;
$pluginAlias = 'user';
$pluginId = 1;
$reference = new PluginReference($companyId, $pluginAlias, $pluginId);
Connector::setReference($reference);
$settingModel = new Settings();
$settingModel->deliveryType = 'Посылка до 10 кг';
$settingModel->packType = 'Коробка "S"';
$settingModel->save();
$findModel = Settings::find();
echo $findModel->deliveryType; // возвращает 'Посылка до 10 кг'
echo $findModel->packType; // возвращает 'Коробка "S"'
$findModel = Settings::find();
$findModel->deliveryType = 'Посылка до 5 кг';
$findModel->save();
echo Settings::find()->deliveryType; // возвращает 'Посылка до 5 кг'
Как видно в примере, поиск модели осуществляется с помощью метода find()
, он не принимает ни каких параметров, так как в базе может существовать только один объект данного класса, для каждого набора companyId + pluginAlias + pluginId + id
. То есть, если в Connector::setReference
, передать PluginReference
, с другим набором companyId
, pluginAlias
, pluginId
, после чего, сохранить модель, то в базе, будет создана еще одна запись, соответствующая текущим companyId + pluginAlias + pluginId + id
.
Схема - это массив, содержащий в себе название поля(индекс) и его тип(значение). Пример схемы:
[
'name' => ['VARCHAR(255)'],
'age' => ['INT'],
'weight' => ['FLOAT']
];
Тип поля может быть любой, поддерживаемый ваше базой данных.
Для этого создадимconsole.php
:
<?php
require_once 'vendor/autoload.php';
use Leadvertex\Plugin\Components\Db\Commands\CreateTablesCommand;
use Leadvertex\Plugin\Components\Db\Components\Connector;
use Medoo\Medoo;
use Symfony\Component\Console\Application;
Connector::config(new Medoo([
'database_type' => 'sqlite',
'database_file' => __DIR__ . '/testDB.db'
]));
$application = new Application();
$application->add(new CreateTablesCommand());
$application->run();
И выполним консольную команду php console.php db:create-tables
, команда создаст все таблицы, для всех моделей с пространством имен Leadvertex\Plugin
.
В примере, мы сначала инициализируем базу, при помощи класса \Leadvertex\Plugin\Components\Db\Components\Connector
. Для этого, используем статичный метод config
, в который передаем объект класса Medoo
документация Medoo.
Вы можете не писать свой console.php
, а воспользоваться написанным, для этого просто выполните команду для создания таблиц.
Обратите внимание, для корректной работы компонента, при использовании sqlite, необходимо, что-бы файл базы и директория в которой он расположен, были доступны для записи.
Есть 4 метода для поиска:
-
public static function findById(string $id): ?self
метод принимает$id
, и возвращает найденную модель. -
public static function findByIds(array $ids): array
метод принимает на вход массив$ids
и возвращает массив найденных моделей. -
public static function findByCondition(array $where): array
метод принимает массив$where
и возвращает массив найденных моделей. -
public static function find(): ?Model
- ничего не принимает и возвращает одну модель, может использоваться только для модели, которая имеет один экземпляр и реализуюетSinglePluginModelInterface
.
Это массив условий для поиска. Подробнее о синтаксисе where
в документации Medoo
.
Приведем несколько примеров использования:
Пусть в таблице есть несколько моделей User
id | name | age |
---|---|---|
0f8169f8-ffb6-4801-8791-8008476acbeb | Sasha | 25 |
750cb54c-c35e-4e11-a9cd-37559cce7a0c | Marina | 25 |
7748576c-8d8f-4d70-8fa8-bc1a4cb78f9f | Viktoria | 18 |
f81c1ccb-72c4-4e97-91e4-8418320a43eb | Daria | 38 |
31fa349b-31f2-4947-80ea-c80a201118ec | Dima | 40 |
$models = User::findByCondition(['name' => 'Marina']);
print_r($models); /* возвращает
Array
(
[750cb54c-c35e-4e11-a9cd-37559cce7a0c] => Leadvertex\Plugin\User Object
(
[name] => Marina
[age] => 25
[id:protected] => 750cb54c-c35e-4e11-a9cd-37559cce7a0c
[_isNew:Leadvertex\Plugin\Components\Db\Model:private] =>
)
)
*/
$models = User::findByCondition(['age' => '25']);
print_r($models); /* возвращает
Array
(
[0f8169f8-ffb6-4801-8791-8008476acbeb] => Leadvertex\Plugin\User Object
(
[name] => Sasha
[age] => 25
[id:protected] => 0f8169f8-ffb6-4801-8791-8008476acbeb
[_isNew:Leadvertex\Plugin\Components\Db\Model:private] =>
)
[750cb54c-c35e-4e11-a9cd-37559cce7a0c] => Leadvertex\Plugin\User Object
(
[name] => Marina
[age] => 25
[id:protected] => 750cb54c-c35e-4e11-a9cd-37559cce7a0c
[_isNew:Leadvertex\Plugin\Components\Db\Model:private] =>
)
)
*/
$models = User::findByCondition(['age[>]' => '30']);
print_r($models); /* возвращает
Array
(
[f81c1ccb-72c4-4e97-91e4-8418320a43eb] => Leadvertex\Plugin\User Object
(
[name] => Daria
[age] => 38
[id:protected] => f81c1ccb-72c4-4e97-91e4-8418320a43eb
[_isNew:Leadvertex\Plugin\Components\Db\Model:private] =>
)
[31fa349b-31f2-4947-80ea-c80a201118ec] => Leadvertex\Plugin\User Object
(
[name] => Dima
[age] => 40
[id:protected] => 31fa349b-31f2-4947-80ea-c80a201118ec
[_isNew:Leadvertex\Plugin\Components\Db\Model:private] =>
)
)
*/
У классов плагина, которые реализуют PluginModelInterface
или SinglePluginModelInterface
, как уже говорилось, автоматически добавляются поля companyId
, pluginAlias
и pluginId
.
При выполнении поиска, данные поля подставляются в каждый запрос, за счет этого, осуществляется безопасное взаимодействие с данными и исключается возможность получить доступ к чужим данным.
Метод find()
позволяет найти модель класса, объект которого может существовать в единственном экземпляре(в прим. Settings
). Потому, что объект может быть только один, не требуется передавать $id
или другую информацию о нем, так как поиск осуществляется по полям companyId
, pluginAlias
и pluginId
.
По этой причине, для подобных классов, стоит использовать только метод find()
, как наиболее удобный.
Учитывая, что остальные классы, которые могут иметь множество экземпляров(в прим. User
и Shipment
), не могут быть найдены только по полям companyId
, pluginAlias
и pluginId
, использование метода find()
с такими моделями невозможно.
Не редка ситуация, когда тип одного из полей вашего класса является сложным. Например, объект другого класса. Для записи и чтения такого поля, необходимо выполнить определенные преобразования в тип, поддерживаемый бд. Приведем примеры реализации подобного класса.
Допустим, у нас есть класс PostOffice
:
class PostOffice
{
private string $address;
private string $postcode;
public function __construct(string $address, string $postcode)
{
$this->postcode = $postcode;
$this->address = $address;
}
public function getPostcode(): string
{
return $this->postcode;
}
public function getAddress(): string
{
return $this->address;
}
}
Мы хотим сохранять его в формате json:
class Shipment extends Model implements PluginModelInterface
{
public PostOffice $postOffice;
public function setPostOffice(PostOffice $postOffice): void
{
$this->postOffice = $postOffice;
}
public function setId($id)
{
$this->id = $id;
}
public function getPostcode(): string
{
return $this->postOffice->getPostcode();
}
public function getAddress(): string
{
return $this->postOffice->getAddress();
}
public static function schema(): array
{
return [
'postOffice' => ['VARCHAR(255)'],
];
}
protected static function beforeWrite(array $data): array
{
$postOffice = $data['postOffice'];
$data['postOffice'] = json_encode(self::postOfficeToArray($postOffice));
return $data;
}
protected static function afterRead(array $data): array
{
$postArray = json_decode($data['postOffice'], true);
$data['postOffice'] = new PostOffice($postArray['address'], $postArray['postcode']);
return $data;
}
private static function postOfficeToArray(PostOffice $postOffice): array
{
return ['address' => $postOffice->getAddress(), 'postcode' => $postOffice->getPostcode()];
}
}
Как видно в примере, мы переопределили два метода beforeWrite
и afterRead
.
Метод beforeWrite
выполняется перед записью, а метод afterRead
сразу после чтения.
Оба метода принимают на вход массив $data, в котором содержатся все поля класса и возвращают его же.
Запишем модель в базу:
$companyId = 1;
$pluginAlias = 'user';
$pluginId = 1;
Connector::setReference(new PluginReference($companyId, $pluginAlias, $pluginId));
$postOffice = new PostOffice('City', '123456');
$shipmentModel = new Shipment();
$id = Uuid::uuid4();
$shipmentModel->setId($id);
$shipmentModel->setPostOffice($postOffice);
$shipmentModel->save();
В базе:
companyId | pluginAlias | pluginId | id | postOffice |
---|---|---|---|---|
1 | user | 1 | 1df95f06-c881-43fa-8cf6-6cd1c9e01f70 | {"address":"City","postcode":"123456"} |
И найдем ее:
$findModel = Post::findById($id);
echo $findModel->getAddress(); // возвращает 'City'
echo $findModel->getPostcode(); // возвращает '123456'
Использовать метод serialize не рекомендуется, т.к. может измениться версия php и может возникнуть ситуация, что данные в базе не получится прочитать. Лучше использовать формат json.
Это класс, который позволяет сохранять объекты дочерних классов в базу данных. Для этого в нем реализованы методы:
public function getId(): string
public function save(): void
public function delete(): void
public function isNewModel(): bool
protected function beforeSave(bool $isNew): void
protected function afterFind(): void
public static function findById(string $id): ?self
public static function findByIds(array $ids): array
public static function findByCondition(array $where): array
public static function find(): ?Model
public static function addOnSaveHandler(callable $handler, string $name = null): void
public static function removeOnSaveHandler(string $name): void
public static function tableName(): string
abstract public static function schema(): array;
public static function freeUpMemory(): void
protected static function afterRead(array $data): array
protected static function beforeWrite(array $data): array
protected static function db(): Medoo
Данные методы позволяют добавить и удалить обработчик(callable $handler
).
Вызов обработчика будет происходить во время сохранения модели в базу, то есть во время вызова метода save
.
Это может быть полезно, когда необходимо выполнить какие-то действия, непосредственно в момент сохранения модели.
Методы beforeSave
и afterFind
ничего не возвращают, в отличие от рассмотренных выше beforeWrite
и afterRead
. Данные методы можно переопределить для выполнения любого, необходимого кода.
При выполнении метода save
или одного из find
, сначала вызывается beforeWrite
или afterRead
, после чего beforeSave
или afterFind
, соответственно.
Данный метод нужен для вызова любых sql-запросов после создания таблицы при вызове db:create-tables
.
Например, нам необходимо задать индексы для нашей таблицы (для sqlite нельзя сразу создать индексы при CREATE TABLE запросе) или же
мы хотим заполнить таблицу какими-то данными.
Данный метод является статическим и возвращает имя таблицы, в которой хранятся объекты класса.
Данный метод является статическим и используется для очистки памяти.
Все найденные модели, хранятся в единственном экземпляре в оперативной памяти. То есть, если, например в базе:
id | name | age |
---|---|---|
0f8169f8-ffb6-4801-8791-8008476acbeb | Sasha | 25 |
31fa349b-31f2-4947-80ea-c80a201118ec | Dima | 40 |
И мы ищем модели:
$model_1 = User::findByCondition(['name' => 'Sasha']);
$model_2 = User::findByCondition(['age' => '25']);
Найденные модели будут идентичны:
$model_1 === $model_2;
В ситуации, когда вам нужно найти большое количество моделей(> 1000), может не хватить оперативной памяти. В таком случае, рекомендуется осуществлять поиск итеративно, выполняя freeUpMemory() после каждой итерации.
Это класс, который позволяет установить соединение с базой данных. Для этого в нем реализованы методы:
public static function config(Medoo $medoo): void
public static function db(): Medoo
public static function hasReference(): bool
public static function getReference(): PluginReference
public static function setReference(PluginReference $reference)
Данный метод принимает объект класса Medoo
Документация Medoo и используется для инициализации базы, как в примерах выше.
Данный метод принимает объект класса PluginReference
и используется для создания ссылки на плагин, как в примерах выше.
Это класс, который позволяет установить ссылку на плагин. Метод принимает три поля в конструктор:
-
companyId
- Идентификатор компании, которая использует плагин. -
pluginAlias
- Псевдоним плагина(от чьего имени исполняется) -
pluginId
- Идентификатор плагина Данный класс используется вConnector
.