-
Notifications
You must be signed in to change notification settings - Fork 1
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 создаются дополнительные поля, а именно:
companyIdpluginAliaspluginId
Аналогично прошлому примеру, поля должны быть 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(): stringpublic function save(): voidpublic function delete(): voidpublic function isNewModel(): boolprotected function beforeSave(bool $isNew): voidprotected function afterFind(): voidpublic static function findById(string $id): ?selfpublic static function findByIds(array $ids): arraypublic static function findByCondition(array $where): arraypublic static function find(): ?Modelpublic static function addOnSaveHandler(callable $handler, string $name = null): voidpublic static function removeOnSaveHandler(string $name): voidpublic static function tableName(): stringabstract public static function schema(): array;public static function freeUpMemory(): voidprotected static function afterRead(array $data): arrayprotected static function beforeWrite(array $data): arrayprotected 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): voidpublic static function db(): Medoopublic static function hasReference(): boolpublic static function getReference(): PluginReferencepublic static function setReference(PluginReference $reference)
Данный метод принимает объект класса Medoo Документация Medoo и используется для инициализации базы, как в примерах выше.
Данный метод принимает объект класса PluginReference и используется для создания ссылки на плагин, как в примерах выше.
Это класс, который позволяет установить ссылку на плагин. Метод принимает три поля в конструктор:
-
companyId- Идентификатор компании, которая использует плагин. -
pluginAlias- Псевдоним плагина(от чьего имени исполняется) -
pluginId- Идентификатор плагина Данный класс используется вConnector.