В БУС существует глобальный экземпляр пользователя $USER. Вполне логично для нужд конкретного проекта добавить в пользователя свои свойства и методы. Но есть ли легальный способ расширить класс пользователя? Я делаю так
Создаю свой класс пользователя
class User extends \CUser {
public function __construct(\CDBResult $bxUser = null) {
parent::__construct();
if($bxUser && !is_array($this->arResult[0])) {
$this->arResult[0] = $bxUser->Fetch();
}
}
//свои свойства и методы
}
Далее в обработчике подменяю дефолтного юзера
public function onBeforeProlog() {
/** @global \CUser $USER */
global $USER;
/** @global \CMain */
global $APPLICATION;
$GLOBALS['USER'] = $USER = new User(\CUser::GetByID($USER->GetID()));
}
и теперь везде $USER экземпляр моего класса.
Какие могут быть проблемы такого подхода? Может есть путь красивее?
В сложных информационных системах зачастую необходимо видеть отладочную информацию на боевом сайте, из актуальной базы. Чтобы такая информация была всегда под рукой - сделал небольшое дополнение, на основе хелпера Kint. Теперь отладочная информация доступна администратору и всем пользователям панели управления Битрикса. https://github.com/itrukhin/bxkint
Запилил свой адаптер для Monolog`a Пока мне нужен только файловый лог, но логгируется довольно много всякого и хотелось бы файлы логов хранить аккуратно и упорядоченно. Данное минималистичное решение как раз позволяет это сделать.
Решил подключить композер к Битриксу и посмотреть что полезного из этого можно извлечь. Сделал, как описано в документации, по фен-шую, вынеся композер за пределы DOCUMENT_ROOT. В этом случае для подключения битриксового bitrix/composer-bx.json используется Composer Merge Plugin который работает только с Composer 1.x. Еще, как оказалось, композеру нужен unzip которого по умолчанию на BitrixVM нет. Итак, памятка по установке композера на BitrixVM
который позволяет использовать для хранения настроек .env файлы. Как написано в документации - компонент Dotenv анализирует файлы .env, чтобы сделать переменные окружения, хранящиеся в них, доступными через getenv(), $_ENV или $_SERVER. Такой способ, на мой взгляд, гораздо удобнее привычных .settings.php и dbconn.php, позволяет хранить все пароли и настройки в одном месте. За счет этого битриксовые файлы настроек можно будет добавить в систему контроля версий как на продакшене так и на всех дев-серверах.
Далее возник вопрос подключения автозагрузки и Dotenv для использования их в конфигурации 1С-Битрикс. На большинстве страниц первым подключается файл dbconn.php а затем .settings.php. Но в обработчике bitrix/urlrewrite.php почему то обратный порядок подключения конфигов. Поэтому код подключения добавил в оба файла настроек. Теперь dbconn.php начинается так
В предыдущей заметке Объекты сущностей инфоблоков был упомянут менеджер событий, позволяющий реализовывать код обработчиков в классах сущностей. Приведу здесь реализацию.
Для работы менеджера необходимо использовать следующий формат имен классов сущностей: PrefixIblockCodeElement и PrefixIblockCodeSection. Таким образом, используя префикс My - для инфоблока с кодом Company класс элемента будет MyCompanyElement, раздела MyCompanySection.
Сам класс менеджера:
<?php
class ObjEventManager {
static $iblocks;
private $cache_id;
private $cache_dir;
private $ttl = 3600;
public function __construct() {
// кешированный список инфоблоков $this->iblocks
$this->cache_id = 'my_iblock';
$this->cache_dir = '/my_iblock';
$cache = new CPHPCache();
if ($cache->InitCache($this->ttl, $this->cache_id, $this->cache_dir)) {
self::$iblocks = $cache->GetVars();
} elseif (CModule::IncludeModule("iblock") && $cache->StartDataCache()) {
$res = CIBlock::GetList(array(), array('CHECK_PERMISSIONS' => 'N'), false);
while($iblock = $res->Fetch()) {
self::$iblocks[$iblock['ID']] = $iblock;
}
if(count(self::$iblocks) > 0) {
$cache->EndDataCache(self::$iblocks);
} else {
$cache->AbortDataCache();
}
} else {
$this->iblocks = array();
}
// регистрируем все события инфоблоков
foreach(get_class_methods(get_class($this)) as $method) {
// если метод начинается на On
if(strpos($method, 'On') === 0) {
AddEventHandler("iblock", $method, Array(get_class($this), $method));
}
}
}
private static function getClassMethod($iblock_id, $element_type, $method) {
$class_method = false;
if(is_array(self::$iblocks) && array_key_exists($iblock_id, self::$iblocks)) {
$class_prefix = defined('OBJ_IBLOCK_PREFIX') ? OBJ_IBLOCK_PREFIX : '';
$class_name = $class_prefix . ucfirst(self::$iblocks[$iblock_id]['CODE']) . $element_type;
if(class_exists($class_name) && method_exists($class_name, $method)) {
$class_method = array($class_name, $method);
}
}
return $class_method;
}
/********************************************* ELEMENTS *************************************************/
public static function OnBeforeIBlockElementAdd(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Element', 'OnBeforeIBlockElementAdd');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnStartIBlockElementAdd(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Element', 'OnStartIBlockElementAdd');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnAfterIBlockElementAdd(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Element', 'OnAfterIBlockElementAdd');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnBeforeIBlockElementUpdate(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Element', 'OnBeforeIBlockElementUpdate');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnStartIBlockElementUpdate(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Element', 'OnStartIBlockElementUpdate');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnAfterIBlockElementUpdate(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Element', 'OnAfterIBlockElementUpdate');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnBeforeIBlockElementDelete($ID) {
}
public static function OnAfterIBlockElementDelete($ID) {
}
public static function OnIBlockElementDelete($ID) {
}
public static function OnAfterIBlockElementSetPropertyValues($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUES, $PROPERTY_CODE) {
$method = self::getClassMethod($IBLOCK_ID, 'Element', 'OnAfterIBlockElementSetPropertyValues');
if($method) return call_user_func_array($method, array($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUES, $PROPERTY_CODE));
}
public static function OnAfterIBlockElementSetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUES, $FLAGS) {
$method = self::getClassMethod($IBLOCK_ID, 'Element', 'OnAfterIBlockElementSetPropertyValuesEx');
if($method) return call_user_func_array($method, array($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUES, $FLAGS));
}
/********************************************* SECTIONS *************************************************/
public static function OnBeforeIBlockSectionAdd(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Section', 'OnBeforeIBlockSectionAdd');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnAfterIBlockSectionAdd(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Section', 'OnAfterIBlockSectionAdd');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnBeforeIBlockSectionUpdate(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Section', 'OnBeforeIBlockSectionUpdate');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnAfterIBlockSectionUpdate(&$arFields) {
$method = self::getClassMethod($arFields['IBLOCK_ID'], 'Section', 'OnAfterIBlockSectionUpdate');
if($method) return call_user_func_array($method, array($arFields));
}
public static function OnBeforeIBlockSectionDelete($ID) {
}
public static function OnAfterIBlockSectionDelete($ID) {
}
}
Для его подключения в init.php должен быть объявлен префикс и создан объект:
define('OBJ_IBLOCK_PREFIX', 'My');
new ObjEventManager();
Не реализованы вызовы для событий удаления, но их можно дописать в соответствии с потребностями конкретного проекта.
Пример использования - создание обработчика, сбрасывающего кеш при OnAfterIBlockElementSetPropertyValuesEx
<?php
class MyCompanyElement extends ObjElement {
static $iblock_id = 3;
public static function OnAfterIBlockElementSetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUES, $FLAGS) {
global $CACHE_MANAGER;
$CACHE_MANAGER->ClearByTag("iblock_id_" . $IBLOCK_ID);
}
}
В битриксе есть замечательная универсальная сущность – инфоблок, с помощью которой удобно хранить и структурировать различные данные. Но вот работать с универсальными данными не всегда удобно. Для реализации бизнес-логики предпочтительнее оперировать специальными объектами и при этом хранить данные, конечно, в инфоблоках.
Создавая очередной класс некой сущности, делая в нем getList и getById, и реализуя кеширование данных – захотелось как то унифицировать этот процесс.
Идея была воплощена в виде универсальных классов для инфоблоков. Здесь я не буду останавливаться на их реализации – это можно сделать скачав код. Опишу применение, чтобы можно было оценить удобство работы.
Основные операции с элементами для чтения - это получение списка с определенной сортировкой и фильтром, а также получение элемента по ИД, как частный случай.
Мы создаем для каждой сущности класс, являющийся наследником базового класса, устанавливая у него значение свойства ИД инфобока. Например компании:
class MyCompanyElement extends ObjElement {
static $iblock_id = 3;
}
Чтобы получить элемент типа MyCompanyElement с ИД = 5 нужно использовать метод:
$company = MyCompanyElement::getById(5);
- вернет объект типа MyCompanyElement, соответствующий элементу инфоблока с ИД 5
Чтобы получить список активных элементов инфоблока, отсортированных, например, по имени – нужно использовать метод:
Результатом будет объект типа ObjCachedList, реализующий интерфейсы ArrayAccess, Countable, Iterator и содержащий в себе список объектов типа MyCompanyElement. Таким образом:
$element = $list[45]; /* элемент типа MyCompanyElement с ИД = 45 */
Цикл по элементам списка с ключом соответствующим ИД элемента (инфоблока), при этом $element – элемент типа MyCompanyElement.
Обратите внимание на phpdoc конструкцию внутри цикла. Она позволяет для IDE (Php Storm) правильно определять тип и выводить в подсказке все методы класса.
$cnt = count($list); /* количество элементов списка. */
Теперь, имея возможность легко создавать классы для сущностей бизнес логики – мы можем реализовать для них различные специфические методы, соответствующие именно этим сущностям.
Представим, что есть инфоблок Employee элементы которого привязаны к элементам инфоблока Company через свойство COMPANY. Создаем класс
class MyEmployeeElement extends ObjElement {
static $iblock_id = 4;
}
И добавляем метод получения связанных элементов в класс MyCompanyElement
class MyCompanyElement extends ObjElement {
static $iblock_id = 3;
public function getEmployees($sort = array(), $filter = array()) {
$filter['PROPERTY_COMPANY'] = $this->getId();
return MyEmployeeElement::getList($sort, $filter);
}
}
Тогда получение списка всех сотрудников компании с ИД 5 реализуется как
$company = MyCompanyElement::getById(5);
$employees = $company-> getEmployees(); /* объект типа ObjCachedList, содержащий список объектов типа MyEmployeeElement */
Классы списков ObjCachedList и ObjCachedSectList реализуют управляемое кеширование элементов и разделов, зависящее от тега “iblock_id_XX”. Так как все операции с инфоблоками кешируются – удобно и легко делать свои простые компоненты на основе пустого компонента. Или не делать компоненты . Аналогично элементам реализован класс для разделов инфоблоков. Наверное, было бы полезно и для самих инфоблоков, но у меня пока такой потребности не возникло.
Идея классов-сущностей может стать еще полезнее, если сделать в них возможность реализации обработки событий для определенного инфоблока. Как мне видится – это менеджер событий, при возникновении события определяющий объект какого инфоблока вызвал событие, затем проверяющий для инфоблока с кодом IblockCode наличие класса PrefixIblockCodeElement и в нем соотв. метода, например PrefixIblockCodeElement:: OnAfterIBlockElementAdd. Ведь практически все обработчики событий инфоблоков сначала определяют какой тип инфоблока вызвал событие, и только потом делают какие то операции. В этом случае стало бы намного удобнее и логичнее организовывать код обработчиков. Пример менеджера.
Сейчас сам активно использую эту схему в проекте. Надеюсь на конструктивные замечания и предложения по развитию идеи.
P.S. требование - php не ниже 5.3 и так как классы используют позднее связывание – у меня возникли проблемы с акселератором APC – пришлось его отключить.
Использование отладчика при разработке на Битрикс очень сильно облегчает жизнь. Возможность просматривать в рантайме все многомерные массивы и переменные этой системы позволяет сильно сэкономить время и нервы.
Главное, потом не забыть устранить эти ошибки. Мы их проверяем вот так: http://dev.1c-bitrix.ru/community/web...blog/1971/ При включенном тесте производительности очень хорошо видны ошибочные адреса - они отражаются в обращениях к urlrewrite.php
Иногда дизайн страницы требует размещать капчу на соответствующем фоне. Стандартная капча Битрикса при всем множестве настроек не дает такой возможности.
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».