SLOT-H: Принципы ORM
ORM в системе построена на двух ключевых идеях: минимализм и предсказуемость. Она не пытается быть универсальным решением для всех задач, а даёт ровно то, что нужно для работы с данными в рамках экосистемы.
Разделение на DbTables и Models
В системе ORM разделена на два слоя:
- DbTables - описание структуры таблицы. Поля, типы, индексы, связи. Никакой логики. Только описание.
- Models - работа с данными. Геттеры, сеттеры, связи, преобразования. Логика работы с объектами.
Это разделение позволяет:
- Менять структуру таблицы без изменения логики работы с данными.
- Менять логику работы с данными без изменения структуры таблицы.
- Использовать DbTables без Models (например, для простых выборок).
- Наследовать Models без изменения DbTables.
Контроллер и модель
В системе контроллер не знает, с какой моделью он работает. Он не содержит объявления модели и не наследует её. Программист в коде экшена явно вызывает нужную модель через статический метод.
Это даёт возможность:
- В одном контроллере работать с разными моделями.
- Легко подменять модель для конкретного проекта.
- Не дублировать логику работы с данными в контроллерах.
Статические методы
Модели и DbTables используют статические методы для работы с данными. Это делает код простым и понятным:
php
$post = Post::getRow($id);
$posts = Post::getAll();
Статический подход не требует инстанцирования объекта для выполнения запросов. Объект создаётся только когда результат получен.
Объект Where
Объект Where описывает одно условие в запросе. Он поддерживает следующие типы условий:
Простые сравнения: EQUAL, NOT_EQUAL, MORE, LESS, MORE_EQUAL, LESS_EQUAL
Проверки на NULL: IS_NULL, NOT_NULL
Поиск по шаблону: LIKE, LIKE_START, LIKE_END, LIKE_EQUAL и их отрицания NOT_LIKE, NOT_LIKE_START, NOT_LIKE_END, NOT_LIKE_EQUAL
Регулярные выражения: REGEXP, NOT_REGEXP
Поиск в строках с разделителями: FIND_IN_SET, NOT_FIND_IN_SET
Диапазон: BETWEEN
Массовые операции: ARR_EQUAL (IN), ARR_NOT_EQUAL (NOT IN), а также варианты для MORE, LESS, LIKE с группировкой через AND или OR
Объект Block
Объект Block объединяет несколько условий Where или других Block в одну группу. Он формирует выражение с соединителями AND или OR.
Это позволяет строить сложные условия:
sql
(условие1 AND условие2) OR (условие3 AND условие4)
Оптимизация запросов
Все условия WHERE сортируются по весу - от меньшего к большему. Это позволяет выполнять сначала наиболее эффективные условия, использующие индексы.
- NULL проверки имеют вес 0
- Равенство (EQUAL) имеет вес 1
- Сравнения (MORE, LESS, NOT_EQUAL) имеют вес 2
- IN и NOT IN имеют вес 3
- LIKE и регулярные выражения имеют вес 4-5
- Массовые операции (ARR_*) имеют вес 8-10
При формировании запроса условия с меньшим весом выполняются раньше, что ускоряет выполнение запроса.
Базовые классы Database
Все классы работы с базой данных наследуются от Application/Assistance/Database.
Database - базовый класс. Содержит основную логику работы с данными: построение запросов, выполнение, обработка результатов.
DatabaseCache - расширяет базовый класс механизмом кэширования. Результаты запросов сохраняются в статическом кэше или внешнем хранилище.
DatabaseExtend - добавляет поля deleted_at и updated_at. Во все запросы автоматически добавляется условие deleted_at IS NULL (soft delete). Поле updated_at обновляется автоматически при сохранении.
DatabaseExtendProject - добавляет поле project_id. Во все запросы автоматически добавляется условие принадлежности текущему проекту. Проект определяется по домену из запроса.
DatabaseExtendStatus - добавляет статусную фильтрацию. Во все запросы автоматически добавляется условие по списку статусов объекта, если они определены.
DatabaseNormal - прямое наследование от Database без дополнительных полей и условий.
DatabaseNormalProject - аналогично DatabaseExtendProject, но без полей deleted_at и updated_at.
DatabaseNormalStatus - аналогично DatabaseExtendStatus, но без полей deleted_at и updated_at.
Иерархия наследования
text
Database
├── DatabaseNormal
│ ├── DatabaseNormalProject
│ └── DatabaseNormalStatus
├── DatabaseExtend
│ ├── DatabaseExtendProject
│ └── DatabaseExtendStatus
└── DatabaseCache
Выбор класса зависит от того, какие поля и фильтрации нужны для таблицы.
Если таблица не требует мягкого удаления и проектной изоляции - DatabaseNormal
Если требуется мягкое удаление - DatabaseExtend
Если требуется проектная изоляция - DatabaseNormalProject или DatabaseExtendProject
Если требуется фильтрация по статусу - DatabaseNormalStatus или DatabaseExtendStatus
Автоматические связи
По имени поля ORM определяет связь с другой таблицей. Если поле называется country_id, система предполагает, что оно ссылается на таблицу countries и поле country_id. Это работает через FP_LINK в описании поля.
Для работы со связями в моделях используются методы:
linkField() - получение связанного объекта (one-to-one)
lotsFieldInModuleByField() - получение коллекции связанных объектов (one-to-many)
relatedFieldInModuleByField() - получение обратной связи (belongs-to)
Всё это генерируется автоматически на основе описания полей в DbTables.
Минимализм в моделях
Модель не содержит логики. Она только объявляет свойства и наследуется от базового класса. Все геттеры, сеттеры и методы работы со связями находятся в базовом классе Model.
Это делает модели максимально короткими и понятными.
Работа с данными через Select
Запросы строятся через объект Select, который собирает SQL-запрос по частям. Это позволяет:
Строить запросы любой сложности
Добавлять условия через Where и Block
Использовать агрегатные функции (COUNT, MAX, MIN, SUM)
Управлять кешированием на уровне запроса
Select не выполняет запрос самостоятельно. Он передаётся в DbEngine, который работает с конкретной СУБД. Тип движка указывается в ini-файле конкретного проекта параметром db_engine. Это позволяет на одном ядре одновременно обслуживать несколько сайтов, каждый из которых может работать на своей СУБД (MySQL, PostgreSQL, MongoDB). Единственное ограничение - производительность сервера.
Типизация полей
Каждое поле в DbTables описывается с типом. Это позволяет:
Приводить значения к нужному типу при установке
Форматировать значения при получении
Оптимизировать запросы с учётом типа поля
Основные типы: INT, FLOAT, STRING, TEXT, DATE, DATETIME, JSON, BOOLEAN.
В классе DbTables каждое поле определяется в свойстве $_fields вида (из примера ранее):
php
public static $_fields = [
self::POST_ID => [],
self::POST_TEXT => [self::FP_TYPE => self::TYPE_TEXT],
self::POST_AUTHOR => [self::FP_TYPE => self::TYPE_STRING],
self::POST_TIME => [],
];
Ключ - поле, значение - массив для опционального переопределения значений.
Полный список значений, поступающий в ORM для каждого поля:
php
/** @var array Default field type template */
protected $_typeTemplate = [
db::FP_TYPE => db::TYPE_INT,
db::FP_NULL => false,
db::FP_TRIM => true,
db::FP_DEFAULT => 0,
db::FP_INDEX => false,
db::FP_CACHE => false,
db::FP_LENGTH => null,
];
Из этого следует, что для объявления поля как числовое, не равное нулю достаточно оставить значение пустым массивом:
php
self::POST_TIME => []
Для определения поля как строки достаточно изменить его тип:
php
self::POST_AUTHOR => [self::FP_TYPE => self::TYPE_STRING]
Кеширование
ORM поддерживает два уровня кеширования:
Статический кеш на уровне запроса. Результаты запросов сохраняются на время выполнения скрипта.
Внешний кеш через DatabaseCache. Результаты сохраняются во внешнем хранилище (Redis, Memcached) для использования между запросами.
Кеширование управляется через параметры запроса и не требует изменения логики работы с данными.
Проектная изоляция
Если таблица наследуется от DatabaseNormalProject или DatabaseExtendProject, ORM автоматически добавляет условие по project_id во все запросы. Это гарантирует, что пользователь видит только данные своего проекта.
Для отключения проектной изоляции используются методы setIgnoreProject() и getIgnoreProject().
Soft delete
Если таблица наследуется от DatabaseExtend, ORM автоматически добавляет условие deleted_at IS NULL во все запросы. Это реализует мягкое удаление.
Для отключения soft delete используется setIgnoreExtend().
Итог
ORM в системе - это не тяжеловесный фреймворк. Это набор соглашений и базовых классов, которые позволяют работать с данными быстро, предсказуемо и без лишнего кода. Она не скрывает SQL, а даёт инструменты для его построения. Она не навязывает архитектуру, а предлагает путь.
Что дальше?
- ➡️ Раздел 17. Методы работы
- ➡️ Раздел 18. Использование JOIN
- ➡️ Раздел 19. Модели