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

Я не буду в этих статьях рассуждать об элегантности разработанных решений, т.к. это бессмысленно (все итак знают - почему). Постараюсь сосредоточиться именно на том, какие функции битриксоиды скрывают от нас, а также как их использовать.

Мне по долгу службы приходится много и часто ковыряться в ядре битрикса, поэтому волей-неволей натыкаешься на всякие разности. В этот раз расскажу о том, как можно заставить битрикс работать с вашими собственными сущностями модуля «Интернет-магазин».

Реестр классов

Есть в d7 такой класс - \Bitrix\Sale\Registry. Это синглтон (в битрикс очень любят синглтоны), который содержит много констант и маппинг имен классов, который задает соответствие между «типом сущности» и «классом сущности». В момент создания инстанса синглтона битрикс каждой сущности назначает свое имя класса, а когда нужно получить экземпляр той или иной сущности, то битрикс с помощью этого Registry создает инстансы тех классов, которые прописаны в маппинге.

К счастью для нас (простых смертных разработчиков), битриксоиды сделали возможность переопределить стандартные классы, используя методы setRegistry или set.

Как это можно применить?

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

Итак. Создаем свой класс, который будет наследоваться от \Bitrix\Sale\Order (для того, чтобы обеспечить целостность контракта). Можно создать полностью автономный класс, не связанный с родителем, но нужно вручную будет реализовать все остальные методы, которые входят в определенные битриксом интерфейсы, а также отнаследоваться от класса \Bitrix\Sale\OrderBase, однако все это будет не очень-то удобно и громоздко. Да и к тому же нет уверенности в том, что битрикс действительно описал все необходимые интерфейсы (или контракты) и следовал им.

В своем классе создаем нужные методы:

namespace Maximaster\Sale;

use Bitrix\Sale\PropertyValue;

class Order extends \Bitrix\Sale\Order
{
    /**
     * Получает значение свойства по его коду
     * @param $code
     * @return mixed
     */
    public function getOrderPropertyByCode($code)
    {
        /** @var PropertyValue $property */
        foreach ($this->getPropertyCollection() as $property) {
            if ($property->getField('CODE') === $code) {
                return $property->getValue();
            }
        }

        return null;
    }

    /**
     * Получает значение свойства "Телефон"
     * @return string|null
     */
    public function getPhone()
    {
        return $this->getOrderPropertyByCode('PHONE');
    }

    /**
     * Получает строковое представление адреса доставки заказа
     * @return string
     */
    public function getAddress()
    {
        return implode(', ', array_filter([
            $this->getOrderPropertyByCode('LOCATION_NAME'),
            $this->getOrderPropertyByCode('STREET'),
            $this->getOrderPropertyByCode('HOUSE'),
            $this->getOrderPropertyByCode('APARTMENT')
        ]));
    }
}

Далее нам необходимо этот класс зарегистрировать в «Реестре». Сделать это нужно где-нибудь пораньше, в init.php или лучше на событии OnPageStart, примерно вот так:

if (Loader::includeModule('sale')) {
    Registry::getInstance(Registry::REGISTRY_TYPE_ORDER)
        ->set(Registry::ENTITY_ORDER, \Maximaster\Sale\Order::class);
}

Все. Теперь везде, где битрикс будет создавать экземпляр класса заказа через «Реестр», будет создаваться именно наш класс. Во всех обработчиках d7, в админке и везде (теоретически) будет наш класс. Вот примерная демонстрация:

// Грузим заказ через стандартный битриксовый класс

/** @var \Maximaster\Sale\Order $order */
$order = \Bitrix\Sale\Order::load(345277); 

// Убеждаемся, что там инстанс нашего класса
var_dump($order instanceof \Maximaster\Sale\Order); // true

// работаем с нашими кастомными методами
var_dump($order->getPhone()); // Выведет телефон из свойства заказа
var_dump($order->getAddress()); // Выведет строку с адресом из свойств заказа

Использовать на проде или нет - решать вам. Я бы пока не стал 🙂 На днях заметил, что разработчики битрикса изменили один из интерфейсов без уведомления. Подобные изменения могут сыграть злую шутку службе сопровождения сайта … Но если битрикс не обновлять, то все в теории будет ок.