В чем проблема с Ajax-запросами в 1С-Битрикс?
Как известно в версии 17.5.10
главного модуля 1С-Битрикс появились нативные обработчики ajax-запросов у компонентов и модулей. В чем же проблема?
В данной статье я буду обсуждать только ajax-запросы к компонентам, так как проблема именно с ними, а именно, вызов метода компонента происходит статически, и мы не получаем параметров вызова компонента $arParams
. Понятно, что информацию можно передавать в параметрах запроса, но что если параметров очень много или в них содержатся какие-либо секретные данные?
В этой статье я попробовал ответить на этот вопрос и придумать решение.
Если Bitrix не дает нам $arParams
, получим его самостоятельно.
Исходный код из примеров ниже на GitHub Gist: https://gist.github.com/pgooood/cd5169265e5abf4af02859d075dafe5d
Что необходимо чтобы получить $arParams
не имея экземпляра класса компонента?
- Путь к PHP файлу, в котором происходит вызов компонента
- Идентификатор компонента в файле
Эти два параметра придется передать в ajax-запросе, если реальный путь к файлу на сервере — это секретная информация, можно его зашифровать, в своем решении я использую обычный Base64, как защиту от дурака.
Теперь мы имеем все необходимое чтобы достать $arParams
. Что из инструментария битрикса нам понадобится:
- метод
CBitrixComponent::GetEditAreaId($entryId) —
это единственный способ, из тех что я нашел, позволяющий получить идентификатор компонента в рамках файла скрипта, так как другие свойства классаCBitrixComponent
, которые могли помочь, объявлены как приватные. - метод
PHPParser::ParseScript($scriptContent)
- позволяет распарсить содержимое PHP файла и получить информацию о находящихся в нем компонентах, в том числе и желанный$arParams
.
Первое что мы сделаем — это класс компонента реализующий интерфейс Bitrix\Main\Engine\Contract\Controllable
, в котором объявим обработчик аякс-запроса exampleRequest
:
<?php
use Bitrix\Main\Engine\Contract\Controllerable;class ajaxTest extends \CBitrixComponent implements Controllerable{ function executeComponent(){
$this->includeComponentTemplate();
} function configureActions(){
return [
'exampleRequest' => [
'prefilters' => []
,'postfilters' => []
]
];
} function exampleRequestAction(){
return [
/* ответ на аякс-запрос */
];
}}
Далее добавим в класс метод, который будет получать уникальный идентификатор нашего компонента в файле:
protected function componentId(){
$entryId = 'sometext';
$m = null;
/* вычленим только уникальную цифровую часть идентификатора */
if(preg_match(
'/^bx_(.*)_'.$entryId.'$/'
,$this->getEditAreaId($entryId)
,$m
)){
return $m[1];
}
}
Добавим методы для получения информации о компонентах из файла скрипта:
- статический метод
pageComponents
, который будет возвращать все компоненты в заданном PHP файле, добавив в них идентификаторы, по которым мы сможем найти нужный - и статический метод
componentData
возвращающий информацию о компоненте по его идентификатору
protected static function pageComponents($scriptPath){
if(is_file($absPath = $_SERVER['DOCUMENT_ROOT'].$scriptPath)){
$arCounter = [];
$fileContent = file_get_contents($absPath);
$arComponents = \PHPParser::ParseScript($fileContent);
foreach($arComponents as &$r){
$arCounter[$r['DATA']['COMPONENT_NAME']]++;
/* делаем ID как в методе getEditAreaId */
$r['ID'] = abs(crc32($r['DATA']['COMPONENT_NAME']
.'_'.$arCounter[$r['DATA']['COMPONENT_NAME']]));
}
return $arComponents;
}
throw new \Exception('File ['.$scriptPath.'] not found');
}protected static function componentData($componentId,$scriptPath){
if($componentId
&& ($arComponents = self::pageComponents($scriptPath))
){
foreach($arComponents as $arData)
if($componentId == $arData['ID'])
return $arData;
}
}
Теперь добавим в $arResult
необходимые данные: идентификатор компонента и путь к файлу скрипта, для этого изменим наш метод excecuteComponent
следующим образом
function executeComponent(){
$this->arResult = [
'COMPONENT_ID' => $this->componentId()
,'SCRIPT_PATH' => $_SERVER['SCRIPT_NAME']
];
$this->includeComponentTemplate();
}
в примере выше, мы предполагаем, что компонент вызывается в файле, который является точкой входа для http-запроса (например, /index.php
), если компонент расположен в другом месте, например в шаблоне сайта, то для значения SCRIPT_PATH
необходимо задать соответствующий путь.
Перейдем к шаблону компонента, там выполним аякс-запрос сразу при загрузке страницы, передав в качестве параметров запроса идентификатор компонента componentId
и закодированный в Base64 путь к файлу скрипта scriptCast
, при этом не забудем подключить аякс-библиотеку битрикса CJSCore::Init(['ajax']);
, например в файле component_epilog.php
<?php
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();?><script>
BX.ajax.runComponentAction(
'ajaxTest'
,'exampleRequest'
,{
mode: 'class'
,data: {
componentId: <?=$arResult['COMPONENT_ID']?>
,scriptCast: '<?=base64_encode($arResult['SCRIPT_PATH'])?>'
}
})
.then(function(response){
if(response.status === 'success'){
/* запрос выполнен успешно */
}
});
</script>
Чтобы обработать этот запрос и при этом получить исходный массив $arParams
изменим метод exampleRequestAction
в классе компонента следующим образом:
function exampleRequestAction($componentId,$scriptCast){
if(($scriptPath = base64_decode($scriptCast))
&& ($arComponentData = self::componentData(
$componentId
,$scriptPath
))
){
/* полученный $arParams */
var_dump($arComponentData['DATA']['PARAMS']);
}
return [
/* ответ на аякс-запрос */
];
}
На этом все, еще раз ссылка на исходники: https://gist.github.com/pgooood/cd5169265e5abf4af02859d075dafe5d