Тип поля в ORM — это код, отвечающий за преобразование значения между PHP и БД. Создавая собственный класс поля, вы инкапсулируете сериализацию, валидацию и приведение типов там, где им и место — в модели, а не в бизнес-логике.
Зачем свои типы полей
Штатных типов D7 (IntegerField, StringField, DatetimeField и др.) хватает для простых схем. Собственный тип нужен, когда значение имеет структуру или требует особой обработки:
- хранение массива или JSON в одной текстовой колонке;
- прозрачное шифрование/дешифрование значения при записи и чтении;
- нормализация телефона, приведение денежной суммы к копейкам;
- единая валидация формата для всех сущностей проекта.
Смысл в том, чтобы прикладной код работал с удобным PHP-значением (массивом, объектом), а поле само занималось преобразованием к строке для БД и обратно.
Иерархия классов полей
Все скалярные типы наследуются от Bitrix\Main\ORM\Fields\ScalarField, который, в свою очередь, происходит от базового Field. Ключевые уровни:
| Класс | Роль |
|---|---|
Field | Базовый абстрактный класс любого поля |
ScalarField | Поле, отображённое на реальную колонку БД |
ExpressionField | Вычисляемое поле без колонки |
Reference | Связь с другой сущностью |
Свой скалярный тип проще всего унаследовать от готового близкого класса (например, TextField для строкового хранилища) и переопределить преобразование значения.
Save и Fetch модификаторы
Самый быстрый способ добавить сериализацию без нового класса — навесить на существующее поле модификаторы. Метод addSaveDataModifier() преобразует значение перед записью, addFetchDataModifier() — после чтения:
(new TextField('SETTINGS'))
->addSaveDataModifier(fn($v) => json_encode($v, JSON_UNESCAPED_UNICODE))
->addFetchDataModifier(fn($v) => json_decode($v, true));
После этого прикладной код читает и пишет обычный массив, а в БД лежит JSON-строка. Так реализована и штатная сериализация в некоторых полях ядра.
Собственный класс поля
Когда логику нужно переиспользовать в десятках сущностей, вынесите её в отдельный класс. Наследуйтесь от подходящего поля и переопределите преобразование:
namespace Vendor\Orm\Fields;
use Bitrix\Main\ORM\Fields\TextField;
class JsonField extends TextField
{
public function __construct($name, array $parameters = [])
{
parent::__construct($name, $parameters);
$this->addSaveDataModifier([$this, 'encode']);
$this->addFetchDataModifier([$this, 'decode']);
}
public function encode($v) { return $v === null ? null : json_encode($v, JSON_UNESCAPED_UNICODE); }
public function decode($v) { return $v === null ? null : json_decode($v, true); }
}
В getMap() теперь достаточно написать new JsonField('SETTINGS'). Логика централизована, а сущности остаются чистыми.
Готовый ArrayField и его аналоги
Прежде чем писать свой класс, проверьте штатные решения. В ядре есть Bitrix\Main\ORM\Fields\ArrayField — он сериализует массив в строку и обратно, по умолчанию через PHP-сериализацию, но допускает и свой сериализатор:
use Bitrix\Main\ORM\Fields\ArrayField;
(new ArrayField('DATA'))
->configureSerializationJson(); // хранить как JSON
configureSerializationPhp()— форматserialize()(по умолчанию);configureSerializationJson()— читаемый JSON;- можно передать пару собственных колбэков сериализации/десериализации.
Валидаторы значения
Собственный тип поля — удобное место для валидации. Добавьте валидатор через addValidator(), и он сработает при add()/update(), вернув ошибку в объекте Result:
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;
(new StringField('CODE'))
->addValidator(new LengthValidator(2, 32))
->addValidator(function ($value, $primary, $row, $field) {
return preg_match('/^[A-Z0-9_]+$/', $value) ? true : 'Только латиница и цифры';
});
Валидатор возвращает true при успехе или строку/объект ошибки. Встраивая проверки в класс поля, вы гарантируете единые правила во всех сущностях, где это поле используется.
Итог
Свои типы полей ORM держат сериализацию, приведение и валидацию внутри модели. Начните с малого: addSaveDataModifier/addFetchDataModifier или готовый ArrayField. Когда логика повторяется — выносите её в наследника ScalarField. Помните про ограничение: по сериализованным колонкам не построить SQL-фильтр.
Мы в B2Bsite проектируем модель данных под нестандартные сценарии хранения — от структурированных настроек до данных обмена с внешними системами. Если вашему проекту нужна аккуратная ORM-схема с кастомными типами и валидацией, поможем спроектировать и внедрить её без потери производительности.
Частые вопросы
Обязательно ли писать свой класс поля для JSON?
Нет. Часто достаточно навесить addSaveDataModifier и addFetchDataModifier на TextField или взять штатный ArrayField с configureSerializationJson(). Свой класс нужен, когда логику надо переиспользовать во многих сущностях.
От какого класса наследовать свой тип поля?
Для колонки в БД — от ScalarField или его готового наследника, близкого по типу хранения (TextField, StringField). Для вычисляемых значений без колонки используют ExpressionField.
В каком порядке применяются save- и fetch-модификаторы?
Save-модификаторы применяются в порядке добавления при записи, а fetch-модификаторы — при чтении. Это позволяет строить цепочки вроде сериализация плюс сжатие и корректно разворачивать их обратно.
Можно ли фильтровать по сериализованному полю?
Нормально — нет, потому что в колонке лежит blob или JSON-строка. Если по значениям нужен поиск, выносите их в отдельные колонки или таблицу с индексами.
Как добавить валидацию значения поля?
Через addValidator() — можно передать готовый валидатор (например LengthValidator) или замыкание, возвращающее true либо текст ошибки. Валидация срабатывает при add() и update().
Чем ArrayField сериализует данные по умолчанию?
По умолчанию используется PHP-сериализация (serialize). Через configureSerializationJson() можно переключиться на JSON, а также задать собственные колбэки сериализации и десериализации.
Влияет ли кастомный тип поля на скорость выборки?
Сами модификаторы работают в PHP и незначительно нагружают выборку. Главный риск — не тип поля, а невозможность индексировать сериализованные данные, что бьёт по фильтрации больших таблиц.
Поможем с настройкой и поддержкой Для разработчиков
Поможем с настройкой, доработкой и поддержкой Для разработчиков.