Контроллеры — это часть MVC архитектуры, которая отвечает за обработку запроса и генерирование ответа.
Действия
Контроллеры состоят из действий, которые являются основной сутью и их в конечном итоге запрашивает пользователь для получения результата. В одном контроллере может быть одно или несколько действий. Для примера сделаем контроллер с двумя действиями item.add и item.view в модуле example.
Первый шаг — создать в корне модуля файл .settings.php
.
<?php //modules/vendor.example/.settings.php return [ 'controllers' => [ 'value' => [ 'defaultNamespace' => '\Vendor\Example\Controller', ], 'readonly' => true, ] ];
Далее создаётся сам файл контроллера (смотри подробное описание доступных методов):
namespace VendorExampleController; use BitrixMainError; class Item extends BitrixMainEngineController { public function addAction(array $fields):? array { $item = Item::add($fields); if (!$item) { $this->addError(new Error('Could not create item.', {код_ошибки})); return null; } return $item->toArray(); } public function viewAction($id):? array { $item = Item::getById($id); if (!$item) { $this->addError(new Error('Could not find item.', {код_ошибки})); return null; } return $item->toArray(); } }
В действии add (определенным методом Item::addAction
) сначала идёт попытка создания некого Item по переданным $fields
.
Примечание. Массив $fields получается путем автоматического связывания параметров метода и $_REQUEST
.
Если не удалось выполнить создание по каким-то причинам, то возвращаем null и наполняем ошибками сам контроллер. В этом случае ядро сгенерирует ответ:
{ "status": "error", //обратите внимание, что статус автоматически сменился "data": null, "errors": [ { "message": "Could not create item.", "code": {код} } ] }
Иначе добавляем Item и возвращаем из действия его представление в виде массива $item->toArray()
. Таким образом ядро сгенерирует ответ:
{ "status": "success", "data": { "ID": 1, "NAME": "Nobody", //...поля элемента }, "errors": null }
В целом, действие может вернуть не просто скаляры, но и объекты.
В действии view (определенным методом Item::viewAction
) сначала идёт попытка загрузки некого объекта Item по переданному параметру $id
. Важно заметить, что $id будет автоматически получен из $_POST['id']
или $_GET['id']
.
Если данный параметр не найден, то ядро сгенерирует ответ с ошибкой:
{ "status": "error", "data": null, "errors": [ { "message": "Could not find value for parameter {id}", "code": 0 } ] }
Как обратиться к действию контроллера?
Для вызова конкретного аякс-действия нужно знать и пользоваться соглашением по именованию. В нашем случае: Item::addAction -> vendor:example.item.add
Item::viewAction -> vendor:example.item.view
.
vendor:example.item.add, vendor:example.item.view можно использовать для вызова действий через BX.ajax.runAction:
BX.ajax.runAction('vendor:example.item.add', { data: { fields: { ID: 1, NAME: "test" } } }).then(function (response) { console.log(response); /** { "status": "success", "data": { "ID": 1, "NAME": "test" }, "errors": [] } **/ }, function (response) { //сюда будут приходить все ответы, у которых status !== 'success' console.log(response); /** { "status": "error", "errors": [...] } **/ });
Либо можно получить ссылку на действие и послать http-запрос самостоятельно.
/** @var BitrixMainWebUri $uri **/ $uri = BitrixMainEngineUrlManager::getInstance()->create('vendor:example.item.view', ['id' => 1]); echo $uri; // /bitrix/services/main/ajax.php?action=vendor:example.item.view&id=1 // выполняем GET-запрос
Создание контроллеров и действий
Создание контроллеров
Контроллеры должны быть унаследованы от BitrixMainEngineController или его потомков. Контроллеры могут располагаться внутри модуля, либо внутри компонента в файле ajax.php и быть контроллером для компонента.
Создание действий
Создание действий, это создание просто методов в конкретном контроллере. Метод обязан быть public и иметь суффикс Action.
namespace VendorExampleController; class Item extends BitrixMainEngineController { public function addAction(array $fields) { //... } }
Возвращаемое значение действия представляет собой данные ответа, которые будут высланы клиенту.
Если действие возвращает BitrixMainHttpResponse или его наследников, то данный объект и будет отправлен клиенту. Если действие возвращает некие данные, то они должны приводиться к скаляру или объекту, который после будет превращен в JSON и на основе него будет сформирован BitrixMainEngineResponseAjaxJson
.
В целом действие может вернуть не просто скаляры, но и объекты, которые реализуют следующие интерфейсы:
JsonSerializable
BitrixMainTypeContractArrayable
BitrixMainTypeContractJsonable
Либо конкретные наследники BitrixMainHttpResponse:
- Redirect
- JSON
- Типовой JSON
- Файл b_file
- Ресайзенное изображение
- Архив
- Компонент
Создание классов-действий
Есть возможность создавать классы-действия, которые унаследованы от BitrixMainEngineAction
. Подобная возможность может потребоваться, когда необходимо повторно использовать логику в нескольких контроллерах. Например, если реализуется одинаковый протокол обмена в разных модулях (стандартный поиск, выполнение пошаговых действий с прогрессом и тому подобное.). Для использования нужно описать в карте конфигурации контроллера метод configureActions:
class Test extends BitrixMainEngineController { public function configureActions() { return [ 'testoPresto' => [ 'class' => TestAction::class, 'configure' => [ 'who' => 'Me', ], ], ]; } }
И вот сам TestAction:
<?php use BitrixMainEngineAction; class TestAction extends Action { protected $who; //метод для дополнительного конфигурирования из контроллера. Если требуется установить //какие-то значения во внутреннее состояние public function configure($params) { parent::configure($params); $this->who = $params['who']?: 'nobody'; } //основной метод работы. Параметры так же автоматически связываются, как и в методе //аякс-действии public function run($objectId = null) { return "Test action is here! Do you know object with id {$objectId}? Mr. {$this->who}"; } }
Для вызова этого действия нужно обращаться к нему testoPresto, как описано в карте конфигурации. Класс-действие поддерживает пре- и пост-фильтры и по сути ничем не отличается от обычного метода-действия. Смысл метода run() аналогичен другим методам аякс-действиям.
Использование контроллеров внутри компонентов
Предпочтительно создавать и использовать классы контроллеров, которые располагаются в модулях, как указано в данной статье, так как позволяет лучше организовать повторное использование вспомогательного кода и бизнес-логики.
В простых случаях, если компонент самодостаточен и не используется активно с API-модуля, то можно использовать контроллеры внутри компонента.
Жизненный цикл контроллера
При обработке запроса Application создаёт контроллер на основе соглашения по именованию. Далее работу выполняет контроллер:
- Controller::init() будет вызван после того, как контроллер создан и сконфигурирован.
- Контроллер создает объект действия
- Если действие не удалось создать, выкидывается исключение.
- Контроллер вызывает метод подготовки параметров Controller::prepareParams.
- Контроллер вызывает метод Controller::processBeforeAction(Action $action), в случае возврата true выполнение продолжается.
- Контроллер выкидывает событие модуля main {полное_имя_класс_контроллера}::onBeforeAction, в случае возврата EventResult не равном
EventResult::SUCCESS
выполнение блокируется. На данном событии выполняются префильтры. - Контроллер запускает действие
- Параметры действия будут сопоставлены с данными из запроса
- Контроллер выкидывает событие модуля main {полное_имя_класс_контроллера}::onAfterAction. На данном событии выполняются постфильтры.
- Контроллер вызывает метод
Controller::processAfterAction(Action $action, $result)
. - Приложение получает результат выполнения действия и если это данные, то создает
BitrixMainEngineResponseAjaxJson
с этими данными, либо отправляет объект ответа от действия. - Приложение вызывает метод Controller::finalizeResponse($response), передавая финальный вариант ответа, который будет отправлен пользователю после всех событий и подготовок.
- Вывод $response пользователю.
Несколько namespaces
Указание нескольких namespaces в модуле.
В .settings.php можно указать несколько namespaces, помимо defaultNamespace. Это может быть необходимо, когда контроллеры расположены рядом со своими бизнес-сущностями. Например, в некотором модуле «Диск» у нас есть интеграция с облаками.
<?php return [ 'controllers' => [ 'value' => [ 'namespaces' => [ '\Bitrix\Disk\CloudIntegration\Controller' => 'cloud', //cloud - это альяс ], 'defaultNamespace' => '\Bitrix\Disk\Controller', ], 'readonly' => true, ] ];
Теперь у нас доступны для вызова контроллеры, которые расположены в обоих namespaces. Оба из них поддерживают вызов через полное имя действия и через сокращенную запись, так как у нас есть альяс cloud.
Равносильны:
disk.CloudIntegration.Controller.GoogleFile.get disk.cloud.GoogleFile.get disk.Controller.File.get disk.File.get
Вызов модульного контроллера с
подписанными параметрами компонента
Вызов модульного контроллера с подписанными параметрами компонента
В случае, если вам нужно обратиться к контроллеру, реализованному в модуле, из контекста компонента, прокинув подписанные параметры, используйте следующий способ:
BX.ajax.runAction('socialnetwork.api.user.stresslevel.get', { signedParameters: this.signedParameters, // результат $this->getComponent()->getSignedParameters() data: { c: myComponentName, // например, 'bitrix:intranet.user.profile', параметры которого нам будут нужны fields: { //.. } } });
После этого внутри кода действия используйте:
<?php //... public function getAction(array $fields) { //внутри распакованный, проверенный массив параметров $parameters = $this->getUnsignedParameters(); return $parameters['level'] * 100; }
Контроллеры — это часть MVC архитектуры, которая отвечает за обработку запроса и генерирование ответа. Сразу оговоримся, что дальше речь пойдет про компоненты-контроллеры в контексте Bitrix Framework.
Битрикс-контроллер принимает от клиента запрос и возвращает JSON с результатом или ошибкой. Классы-контроллеры содержат одно или несколько методов-действий и являются надстройкой над обычными компонентами, поэтому их нужно размещать в файле class.php
компонента.
Бекенд
Тут все просто: в компоненте нужно реализовать интерфейс Controllerable
, в ктором есть метод configureActions()
. Этот метод возвращает массив с действиями которые можно вызвать. Например, для действия send
нужно будет создать метод sendAction
.
Метод-действие возвращает массив, который затем отдается клиенту в виде JSON.
Если требуется отдавать на сторону клиента ошибки, то нужно реализовать интерфейс Errorable
, в котором есть методы getErrors()
и getErrorByCode()
. Также нужно будет реализовать коллекцию ошибок ErrorCollection
, ее удобнее всего создать в методе onPrepareComponentParams()
.
В качестве простого примера создадим контроллер для формы обратной связи. Компонент пусть называется machaon:feedback
, код контроллера разместится в файле /local/components/machaon/feedback/class.php
.
<?php
//local/components/machaon/feedback/class.php
namespace MachaonComponents;
use BitrixMainError;
use BitrixMainErrorable;
use BitrixMainErrorCollection;
use BitrixMainEngineActionFilter;
use BitrixMainEngineContractControllerable;
use CBitrixComponent;
class FeedbackComponent extends CBitrixComponent implements Controllerable, Errorable
{
protected ErrorCollection $errorCollection;
public function onPrepareComponentParams($arParams)
{
$this->errorCollection = new ErrorCollection();
return $arParams;
}
public function executeComponent()
{
// Метод не будет вызван при ajax запросе
}
public function getErrors(): array
{
return $this->errorCollection->toArray();
}
public function getErrorByCode($code): Error
{
return $this->errorCollection->getErrorByCode($code);
}
// Описываем действия
public function configureActions(): array
{
return [
'send' => [
'prefilters' => [
// здесь указываются опциональные фильтры, например:
new ActionFilterAuthentication(), // проверяет авторизован ли пользователь
]
]
];
}
// Сюда передаются параметры из ajax запроса, навания точно такие же как и при отправке запроса.
// $_REQUEST['username'] будет передан в $username, $_REQUEST['email'] будет передан в $email и т.д.
public function sendAction(string $username = '', string $email = '', string $message = ''): array
{
try {
$this->doSomeWork();
return [
"result" => "Ваше сообщение принято",
];
} catch (ExceptionsEmptyEmail $e) {
$this->errorCollection[] = new Error($e->getMessage());
return [
"result" => "Произошла ошибка",
];
}
}
}
Фронтенд
В js-библиотеке Битрикс уже есть функция для отправки запросов. Далее простой пример, где machaon:feedback
это имя компонента, send
— имя действия, а в data: {}
передаются необходимые данные:
BX.ajax.runComponentAction("machaon:feedback", "send", {
mode: "class",
data: {
"email": "vasya@email.tld",
"username": "Василий",
"message": "Где мой заказ? Жду уже целый час!"
}
}).then(function (response) {
// обработка ответа
});
Если все хорошо, с бэкенда нам вернется:
{
"status": "success",
"data": {
"result": "Письмо отправлено"
},
"errors": []
}
Либо сообщение об ошибке:
{
"status": "error",
"data": {
"result": "Произошла ошибка"
},
"errors": [{
"message": "Не заполено поле Email",
"code": 0,
"customData": null
}]
}
Отправка запроса напрямую
Если не хочется использовать BX.ajax.runComponentAction()
, можно отправить запрос напрямую, например используя jQuery. Нужно отправить запрос на /bitrix/services/main/ajax.php
, он будет выглядеть примерно так:
$.post(
"/bitrix/services/main/ajax.php?mode=class&c=machaon:feedback&action=send",
{
"email": "vasya@email.tld",
"username": "Василий",
"message": "Где мой заказ? Жду уже целый час!"
},
function (response) {
console.log(response);
}
);
В параметре mode
передается обязательное значение class
, в c
передается имя компонента в формате vendor:component
, action
это запускаемый метод.
Эти параметры обязательно должны передаваться в адресе запроса, иначе происходит ошибка.
email
, username
и message
станут затем параметрами $email
, $username
и $message
в методе sendAction()
.
Использование ЧПУ
Также можно в urlrewrite.php
прописать красивый адрес, например /api/rest-component/<component_vendor>/<component_name>/<action>/
.
Для этого добавим в urlrewrite.php
следующий массив:
$arUrlRewrite = [
// ...
[
"CONDITION" => "#^/api/rest-component/([a-zA-Z0-9]+)/([a-zA-Z0-9.]+)/([a-zA-Z0-9]+)/?.*#",
"RULE" => "mode=class&c=$1:$2&action=$3",
"PATH" => "/bitrix/services/main/ajax.php",
],
];
Тогда запрос будет выглядеть понятнее:
function sendFeedback(form) {
const route = "/api/rest-component/machaon/feedback/send/";
const data = $(form).serialize();
$.post(route, data, function (response) {
console.log(response);
});
}
Нюансы
Если в configureActions()
оставить пустой массив prefilters
, то метод-действие будет работать без фильтров.
Но если не указывать ключ prefilters
, то будут применены фильтры по умолчанию — ActionFilterAuthentication
и ActionFilterCsrf
.
public function configureActions(): array
{
return [
'send' => [
'prefilters' => [] // метод sendAction() будет работать без фильтров
]
];
}
public function configureActions(): array
{
return [
'send' => [] // метод sendAction() будет работать у авторизованных пользователей, которые передали CSRF-токен
];
}
Пример ошибки когда пользователь не авторизован и не передал токен:
{
"status": "error",
"data": null,
"errors": [
{
"message": "Необходимо авторизоваться на сайте",
"code": "invalid_authentication",
"customData": null
},
{
"message": "Invalid csrf token",
"code": "invalid_csrf",
"customData": {
"csrf": "dc8adda3ac983217623cf1196dfc5c61"
}
}
]
}
Если требуется CSRF-защита, то для фильтра BitrixMainEngineActionFilterCsrf
нужно передать идентификатор сессии в параметре sessid
.
На бэкенде его можно получить функцией bitrix_sessid()
, на фронтенде — BX.bitrix_sessid()
.
При использовании BX.ajax.runComponentAction()
сессия передается автоматически.
Другие фильтры можно посмотреть в документации
Открытые члены |
|
__construct (Request $request=null) | |
forward ($controller, string $actionName, array $parameters=null) | |
getConfigurationOfActions () | |
getModuleId () | |
isLocatedUnderPsr4 () | |
getActionUri ($actionName, array $params=array(), $absolute=false) | |
getUnsignedParameters () | |
getCurrentUser () | |
setCurrentUser (CurrentUser $currentUser) | |
convertKeysToCamelCase ($data) | |
listNameActions () | |
configureActions () | |
getAutoWiredParameters () | |
getPrimaryAutoWiredParameter () | |
getDefaultAutoWiredParameters () | |
getRequest () | |
getScope () | |
setScope ($scope) | |
getSourceParametersList () | |
setSourceParametersList ($sourceParametersList) | |
run ($actionName, array $sourceParametersList) | |
finalizeResponse (Response $response) | |
generateActionMethodName ($action) | |
redirectTo ($url) | |
getErrors () | |
getErrorByCode ($code) | |
Поля данных |
|
const | SCOPE_REST = ‘rest’ |
const | SCOPE_AJAX = ‘ajax’ |
const | SCOPE_CLI = ‘cli’ |
const | EVENT_ON_BEFORE_ACTION = ‘onBeforeAction’ |
const | EVENT_ON_AFTER_ACTION = ‘onAfterAction’ |
const | ERROR_REQUIRED_PARAMETER = ‘MAIN_CONTROLLER_22001’ |
const | ERROR_UNKNOWN_ACTION = ‘MAIN_CONTROLLER_22002’ |
const | EXCEPTION_UNKNOWN_ACTION = 22002 |
const | METHOD_ACTION_SUFFIX = ‘Action’ |
Защищенные члены |
|
init () | |
getFilePath () | |
processUnsignedParameters () | |
getSaltToUnsign () | |
writeToLogException (Throwable $e) | |
collectDebugInfo () | |
logDebugInfo () | |
prepareParams () | |
processBeforeAction (Action $action) | |
shouldDecodePostData (Action $action) | |
decodePostData () | |
triggerOnBeforeAction (Action $action) | |
processAfterAction (Action $action, $result) | |
triggerOnAfterAction (Action $action, $result) | |
create ($actionName) | |
buildActionInstance ($actionName, array $config) | |
existsAction ($actionName) | |
getDefaultPreFilters () | |
getDefaultPostFilters () | |
buildFilters (array $config=null) | |
appendFilters (array $filters, array $filtersToAppend) | |
removeFilters (array $filters, array $filtersToRemove) | |
getActionConfig ($actionName) | |
setActionConfig ($actionName, array $config=null) | |
runProcessingThrowable (Throwable $throwable) | |
runProcessingException (Exception $e) | |
runProcessingError (Error $error) | |
runProcessingBinderThrowable (BinderArgumentException $e) | |
buildErrorFromException (Exception $e) | |
buildErrorFromPhpError (Error $error) | |
runProcessingIfUserNotAuthorized () | |
runProcessingIfInvalidCsrfToken () | |
addError (Error $error) | |
addErrors (array $errors) | |
Защищенные данные |
|
$errorCollection | |
$request | |
$configurator | |
См. определение в файле controller.php строка 29
◆ __construct()
__construct | ( | Request | $request = null |
) |
Constructor Controller.
- Аргументы
Переопределяется в Tracking.
См. определение в файле controller.php строка 76
77 {
78 $this->scope = self::SCOPE_AJAX;
79 $this->errorCollection = new ErrorCollection;
81 $this->configurator = new Configurator();
82 $this->converter = Converter::toJson();
83
84 $this->init();
85 }
◆ addError()
|
protected |
Adds error to error collection.
- Аргументы
- Возвращает
- $this
См. определение в файле controller.php строка 972
973 {
974 $this->errorCollection[] = $error;
975
976 return $this;
977 }
◆ addErrors()
|
protected |
Adds list of errors to error collection.
- Аргументы
- Возвращает
- $this
См. определение в файле controller.php строка 985
986 {
987 $this->errorCollection->add($errors);
988
989 return $this;
990 }
◆ appendFilters()
|
finalprotected |
См. определение в файле controller.php строка 776
777 {
778 return array_merge($filters, $filtersToAppend);
779 }
◆ buildActionInstance()
|
finalprotected |
- См. также
- Action::__construct
См. определение в файле controller.php строка 638
639 {
640 if (isset($config[‘callable’]))
641 {
642 $callable = $config[‘callable’];
643 if (!is_callable($callable))
644 {
645 throw new ArgumentTypeException(‘callable’, ‘callable’);
646 }
647
648 return new ClosureAction($actionName, $this, $callable);
649 }
650 elseif (empty($config[‘class’]))
651 {
652 throw new SystemException(
653 «Could not find class in description of {$actionName} in {$this::className()} to create instance»,
654 self::EXCEPTION_UNKNOWN_ACTION
655 );
656 }
657
659 $action = new $config[‘class’]($actionName, $this, $config);
660
661 return $action;
662 }
◆ buildErrorFromException()
|
protected |
См. определение в файле controller.php строка 924
925 {
926 if ($e instanceof ArgumentNullException)
927 {
928 return new Error($e->getMessage(), self::ERROR_REQUIRED_PARAMETER);
929 }
930
931 return new Error($e->getMessage(), $e->getCode());
932 }
◆ buildErrorFromPhpError()
|
protected |
См. определение в файле controller.php строка 934
935 {
936 return new Error($error->getMessage(), $error->getCode());
937 }
◆ buildFilters()
|
finalprotected |
Builds filter by config. If there is no config, then we use default filters
- См. также
- BitrixMainEngineController::getDefaultPreFilters() and
- BitrixMainEngineController::getDefaultPostFilters(). If now is POST query and there is no csrf check in config, then we add it.
- Аргументы
- Возвращает
- array|null
См. определение в файле controller.php строка 715
716 {
717 if ($config === null)
718 {
719 $config = array();
720 }
721
722 if (!isset($config[‘prefilters’]))
723 {
724 $config[‘prefilters’] = $this->configurator->wrapFiltersClosure(
725 $this->getDefaultPreFilters()
726 );
727 }
728 if (!isset($config[‘postfilters’]))
729 {
730 $config[‘postfilters’] = $this->configurator->wrapFiltersClosure(
732 );
733 }
734
735 $hasPostMethod = $hasCsrfCheck = false;
736 foreach ($config[‘prefilters’] as $filter)
737 {
738 if ($filter instanceof ActionFilterHttpMethod && $filter->containsPostMethod())
739 {
740 $hasPostMethod = true;
741 }
742 if ($filter instanceof ActionFilterCsrf)
743 {
744 $hasCsrfCheck = true;
745 }
746 }
747
748 if ($hasPostMethod && !$hasCsrfCheck && $this->request->isPost())
749 {
750 $config[‘prefilters’][] = new ActionFilterCsrf;
751 }
752
753 if (!empty($config[‘-prefilters’]))
754 {
755 $config[‘prefilters’] = $this->removeFilters($config[‘prefilters’], $config[‘-prefilters’]);
756 }
757
758 if (!empty($config[‘-postfilters’]))
759 {
760 $config[‘postfilters’] = $this->removeFilters($config[‘postfilters’], $config[‘-postfilters’]);
761 }
762
763 if (!empty($config[‘+prefilters’]))
764 {
765 $config[‘prefilters’] = $this->appendFilters($config[‘prefilters’], $config[‘+prefilters’]);
766 }
767
768 if (!empty($config[‘+postfilters’]))
769 {
770 $config[‘postfilters’] = $this->appendFilters($config[‘postfilters’], $config[‘+postfilters’]);
771 }
772
773 return $config;
774 }
appendFilters(array $filters, array $filtersToAppend)
removeFilters(array $filters, array $filtersToRemove)
◆ className()
|
staticfinal |
Returns the fully qualified name of this class.
- Возвращает
- string
См. определение в файле controller.php строка 67
68 {
69 return get_called_class();
70 }
◆ collectDebugInfo()
|
finalprotected |
◆ configureActions()
- Возвращает
- array
Замещает Controllerable.
Переопределяется в Analytics, File, CalendarAjax, CalendarEntryAjax, ResourceBookingAjax, SyncAjax, ProductSelector, SkuTree, HtmlEditorAjax, Call, Disk, Secretary, AuthCode, Export, LoadExt, PhoneAuth, PhoneNumber, QrCodeAuth, Rating, ComponentController, DefaultController, Widget, Entity, FacebookConversion, AuthFlow, Grabber, File, Csv, Csv, Collector и FileUploader.
См. определение в файле controller.php строка 287
288 {
289 return [];
290 }
◆ convertKeysToCamelCase()
convertKeysToCamelCase | ( | $data | ) |
Converts keys of array to camel case notation.
- См. также
- BitrixMainEngineResponseConverter::OUTPUT_JSON_FORMAT
- Аргументы
- Возвращает
- array|mixed|string
См. определение в файле controller.php строка 257
258 {
259 return $this->converter->process($data);
260 }
◆ create()
|
protected |
Переопределяется в ComponentController и Base.
См. определение в файле controller.php строка 605
606 {
607 $config = $this->getActionConfig($actionName);
608 $methodName = $this->generateActionMethodName($actionName);
609
610 if (method_exists($this, $methodName))
611 {
612 $method = new ReflectionMethod($this, $methodName);
613 if ($method->isPublic() && mb_strtolower($method->getName()) === mb_strtolower($methodName))
614 {
615 return new InlineAction($actionName, $this, $config);
616 }
617 }
618 else
619 {
620 if (!$config && ($this instanceof ContractFallbackActionInterface))
621 {
622 return new FallbackAction($actionName, $this, []);
623 }
624 if (!$config)
625 {
626 throw new SystemException(
627 «Could not find description of {$actionName} in {$this::className()}»,
628 self::EXCEPTION_UNKNOWN_ACTION
629 );
630 }
631
633 }
634
635 return null;
636 }
buildActionInstance($actionName, array $config)
◆ decodePostData()
|
finalprotected |
См. определение в файле controller.php строка 516
516 : void
517 {
518 CUtil::jSPostUnescape();
519 $this->request->addFilter(new PostDecodeFilter);
520 }
◆ existsAction()
|
finalprotected |
См. определение в файле controller.php строка 664
665 {
666 try
667 {
668 $action = $this->create($actionName);
669 }
670 catch (SystemException $e)
671 {
673 {
674 throw $e;
675 }
676 }
677
678 return isset($action);
679 }
const EXCEPTION_UNKNOWN_ACTION
◆ finalizeResponse()
Finalizes response. The method will be invoked when HttpApplication will be ready to send response to client. It’s a final place where Controller can interact with response.
- Аргументы
- Возвращает
- void
См. определение в файле controller.php строка 581
◆ forward()
forward | ( | $controller, | |
string | $actionName, | ||
array | $parameters = null |
||
) |
- Аргументы
-
Controller $controller string $actionName array | null $parameters
- Возвращает
- HttpResponse|mixed
- Исключения
См. определение в файле controller.php строка 95
96 {
97 if (is_string($controller))
98 {
99 $controller = new $controller;
100 }
101
102
103 $controller->request = $this->getRequest();
104 $controller->setScope($this->getScope());
105 $controller->setCurrentUser($this->getCurrentUser());
106
107
108 $result = $controller->run(
109 $actionName,
110 $parameters === null ? $this->getSourceParametersList() : [$parameters]
111 );
112
113 $this->addErrors($controller->getErrors());
114
115 return $result;
116 }
◆ generateActionMethodName()
|
final |
См. определение в файле controller.php строка 600
601 {
602 return $action . self::METHOD_ACTION_SUFFIX;
603 }
◆ getActionConfig()
|
finalprotected |
См. определение в файле controller.php строка 849
850 {
851 $listOfActions = array_change_key_case($this->configurationOfActions, CASE_LOWER);
852 $actionName = mb_strtolower($actionName);
853
854 if (!isset($listOfActions[$actionName]))
855 {
856 return null;
857 }
858
859 return $listOfActions[$actionName];
860 }
◆ getActionUri()
|
final |
Returns uri for ajax end point for the action name. It’s a helper, which uses relative action name without controller name.
- Аргументы
-
string $actionName Action name. It’s a relative action name without controller name. array $params Parameters for creating uri. bool $absolute
- Возвращает
- BitrixMainWebUri
См. определение в файле controller.php строка 176
177 {
178 if (mb_strpos($this->getFilePath(), ‘/components/’) === false)
179 {
180 return UrlManager::getInstance()->createByController($this, $actionName, $params, $absolute);
181 }
182
183 return UrlManager::getInstance()->createByComponentController($this, $actionName, $params, $absolute);
184 }
◆ getAutoWiredParameters()
getAutoWiredParameters | ( | ) |
◆ getConfigurationOfActions()
|
final |
- Возвращает
- array|null
См. определение в файле controller.php строка 131
132 {
133 return $this->configurationOfActions;
134 }
◆ getCurrentUser()
◆ getDefaultAutoWiredParameters()
|
final |
- Возвращает
- Parameter[]
См. определение в файле controller.php строка 311
312 {
313 return [];
314 }
◆ getDefaultPostFilters()
|
protected |
Returns default post-filters for action.
- Возвращает
- array
См. определение в файле controller.php строка 700
701 {
702 return array();
703 }
◆ getDefaultPreFilters()
|
protected |
Returns default pre-filters for action.
- Возвращает
- array
Переопределяется в Mode, ProductForm, ProductSelector, SkuTree, StoreSelector, DefaultElement, Element, Disk, Cookies, DiskFile, Landing, Note, Address, Format, Location, Source, UserFieldConfig, JsonController, BaseReceiver, Application, Configuration, Import, File, ConsentPreview, Base, EntitySelector, Feedback и FileUploader.
См. определение в файле controller.php строка 685
686 {
687 return array(
688 new ActionFilterAuthentication(),
689 new ActionFilterHttpMethod(
690 array(ActionFilterHttpMethod::METHOD_GET, ActionFilterHttpMethod::METHOD_POST)
691 ),
692 new ActionFilterCsrf(),
693 );
694 }
◆ getErrorByCode()
|
final |
Getting once error with the necessary code.
- Аргументы
-
string $code Code of error.
- Возвращает
- Error
Замещает Errorable.
См. определение в файле controller.php строка 1006
1007 {
1008 return $this->errorCollection->getErrorByCode($code);
1009 }
◆ getErrors()
Getting array of errors.
- Возвращает
- Error[]
Замещает Errorable.
См. определение в файле controller.php строка 996
997 {
998 return $this->errorCollection->toArray();
999 }
◆ getFilePath()
|
finalprotected |
См. определение в файле controller.php строка 155
156 {
157 if (!$this->filePath)
158 {
159 $reflector = new ReflectionClass($this);
160 $this->filePath = preg_replace(‘#[\/]+#’, ‘/’, $reflector->getFileName());
161 }
162
163 return $this->filePath;
164 }
◆ getFullEventName()
|
staticfinal |
См. определение в файле controller.php строка 468
469 {
470 return get_called_class() . ‘::’ . $eventName;
471 }
◆ getModuleId()
Returns module id. Tries to guess module id by file path and function
- См. также
- getModuleId().
- Возвращает
- string
См. определение в файле controller.php строка 142
143 {
144 return getModuleId($this->getFilePath());
145 }
◆ getPrimaryAutoWiredParameter()
getPrimaryAutoWiredParameter | ( | ) |
- Возвращает
- Parameter|null
Переопределяется в Basket.
См. определение в файле controller.php строка 303
304 {
305 return null;
306 }
◆ getRequest()
◆ getSaltToUnsign()
|
protected |
Tries to find salt from request. It’s «c» (component name) in general.
- Возвращает
- string|null
См. определение в файле controller.php строка 221
222 {
223 foreach ($this->getSourceParametersList() as $source)
224 {
225 if (isset($source[‘c’]) && is_string($source[‘c’]))
226 {
227 return $source[‘c’];
228 }
229 }
230
231 return null;
232 }
◆ getScope()
- Возвращает
- string
См. определение в файле controller.php строка 332
333 {
334 return $this->scope;
335 }
◆ getSourceParametersList()
|
final |
- Возвращает
- array
См. определение в файле controller.php строка 352
353 {
354 return $this->sourceParametersList;
355 }
◆ getUnsignedParameters()
|
final |
- Возвращает
- mixed
См. определение в файле controller.php строка 189
190 {
191 return $this->unsignedParameters;
192 }
◆ init()
◆ isLocatedUnderPsr4()
|
final |
См. определение в файле controller.php строка 147
147 : bool
148 {
149
150 $firstLetter = mb_substr(basename($this->getFilePath()), 0, 1);
151
152 return $firstLetter !== mb_strtolower($firstLetter);
153 }
◆ listNameActions()
|
final |
Returns list of all
- Возвращает
- array
См. определение в файле controller.php строка 266
267 {
268 $actions = array_keys($this->getConfigurationOfActions());
269 $lengthSuffix = mb_strlen(self::METHOD_ACTION_SUFFIX);
270
271 $class = new ReflectionClass($this);
272 foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
273 {
274 $probablySuffix = mb_substr($method->getName(), -$lengthSuffix);
275 if ($probablySuffix === self::METHOD_ACTION_SUFFIX)
276 {
277 $actions[] = mb_strtolower(mb_substr($method->getName(), 0, -$lengthSuffix));
278 }
279 }
280
281 return array_unique($actions);
282 }
◆ logDebugInfo()
|
finalprotected |
◆ prepareParams()
|
protected |
Prepare params before process action.
- Возвращает
- bool
См. определение в файле controller.php строка 496
497 {
498 return true;
499 }
◆ processAfterAction()
|
protected |
Common operations after process action. If the method returns void or null it means that we don’t want to modify $result.
- Аргументы
- Возвращает
- HttpResponse|mixed|void
Переопределяется в UserFieldConfig.
См. определение в файле controller.php строка 570
◆ processBeforeAction()
|
protected |
◆ processUnsignedParameters()
|
finalprotected |
См. определение в файле controller.php строка 194
195 {
196 foreach ($this->getSourceParametersList() as $source)
197 {
198 if (isset($source[‘signedParameters’]) && is_string($source[‘signedParameters’]))
199 {
200 try
201 {
202 $this->unsignedParameters = ParameterSigner::unsignParameters(
203 $this->getSaltToUnsign(),
204 $source[‘signedParameters’]
205 );
206 }
207 catch (BadSignatureException $exception)
208 {}
209
210
211 return;
212 }
213 }
214 }
◆ redirectTo()
◆ removeFilters()
|
finalprotected |
См. определение в файле controller.php строка 781
782 {
783 $cleanedFilters = [];
784 foreach ($filters as $filter)
785 {
786 $found = false;
787 foreach ($filtersToRemove as $filterToRemove)
788 {
789 if (is_a($filter, $filterToRemove))
790 {
791 $found = true;
792 break;
793 }
794 }
795
796 if (!$found)
797 {
798 $cleanedFilters[] = $filter;
799 }
800 }
801
802 return $cleanedFilters;
803 }
◆ run()
|
final |
- Аргументы
-
$actionName array $sourceParametersList
- Возвращает
- HttpResponse|mixed
- Исключения
См. определение в файле controller.php строка 376
377 {
378 $this->collectDebugInfo();
379
380 $result = null;
381
382 try
383 {
384 $this->setSourceParametersList($sourceParametersList);
385 $this->processUnsignedParameters();
386
387 $action = $this->create($actionName);
388 if (!$action)
389 {
390 throw new SystemException(«Could not create action by name {$actionName}»);
391 }
392
393 $this->attachFilters($action);
394 if ($this->shouldDecodePostData($action))
395 {
396 $this->decodePostData();
397 }
398
402 {
403 $result = $action->runWithSourceParametersList();
404
405 if ($action instanceof Errorable)
406 {
407 $this->errorCollection->add($action->getErrors());
408 }
409 }
410
413 if ($probablyResult !== null)
414 {
415 $result = $probablyResult;
416 }
417 }
418 catch (Throwable $e)
419 {
421 $this->processExceptionInDebug($e);
422 }
423
425
426 return $result;
427 }
runProcessingThrowable(Throwable $throwable)
processAfterAction(Action $action, $result)
processBeforeAction(Action $action)
triggerOnBeforeAction(Action $action)
triggerOnAfterAction(Action $action, $result)
◆ runProcessingBinderThrowable()
См. определение в файле controller.php строка 902
902 : void
903 {
904 $currentControllerErrors = $this->getErrors();
906 if ($errors)
907 {
908 foreach ($errors as $error)
909 {
910 if (in_array($error, $currentControllerErrors, true))
911 {
912 continue;
913 }
914
915 $this->addError($error);
916 }
917 }
918 else
919 {
921 }
922 }
runProcessingException(Exception $e)
◆ runProcessingError()
|
protected |
См. определение в файле controller.php строка 896
897 {
898
899 $this->errorCollection[] = $this->buildErrorFromPhpError($error);
900 }
◆ runProcessingException()
|
protected |
Runs processing exception.
- Аргументы
- Возвращает
- void
Переопределяется в Base.
См. определение в файле controller.php строка 890
891 {
892
893 $this->errorCollection[] = $this->buildErrorFromException($e);
894 }
◆ runProcessingIfInvalidCsrfToken()
|
protected |
Runs processing if csrf token is invalid.
- Возвращает
- void
См. определение в файле controller.php строка 954
955 {
956 $this->errorCollection[] = new Error(‘Invalid csrf token’);
957
958 throw new SystemException(‘Invalid csrf token’);
959 }
◆ runProcessingIfUserNotAuthorized()
|
protected |
Runs processing if user is not authorized.
- Возвращает
- void
См. определение в файле controller.php строка 943
944 {
945 $this->errorCollection[] = new Error(‘User is not authorized’);
946
947 throw new SystemException(‘User is not authorized’);
948 }
◆ runProcessingThrowable()
|
protected |
См. определение в файле controller.php строка 869
870 {
872 {
873 $this->runProcessingBinderThrowable($throwable);
874 }
875 elseif ($throwable instanceof Exception)
876 {
877 $this->runProcessingException($throwable);
878 }
879 elseif ($throwable instanceof Error)
880 {
882 }
883 }
runProcessingError(Error $error)
◆ setActionConfig()
|
finalprotected |
См. определение в файле controller.php строка 862
863 {
864 $this->configurationOfActions[$actionName] = $config;
865
866 return $this;
867 }
◆ setCurrentUser()
- Аргументы
См. определение в файле controller.php строка 245
246 {
247 $this->currentUser = $currentUser;
248 }
◆ setScope()
- Аргументы
- Возвращает
- Controller
См. определение в файле controller.php строка 342
343 {
344 $this->scope = $scope;
345
346 return $this;
347 }
◆ setSourceParametersList()
|
final |
- Аргументы
-
array $sourceParametersList
- Возвращает
- Controller
См. определение в файле controller.php строка 362
363 {
364 $this->sourceParametersList = $sourceParametersList;
365
366 return $this;
367 }
◆ shouldDecodePostData()
|
protected |
См. определение в файле controller.php строка 511
511 : bool
512 {
513 return $this->request->isPost();
514 }
◆ triggerOnAfterAction()
|
finalprotected |
См. определение в файле controller.php строка 584
585 {
586 $event = new Event(
587 ‘main’,
588 static::getFullEventName(static::EVENT_ON_AFTER_ACTION),
589 array(
590 ‘result’ => $result,
591 ‘action’ => $action,
592 ‘controller’ => $this,
593 )
594 );
595 $event->send($this);
596
597 return $event->getParameter(‘result’);
598 }
◆ triggerOnBeforeAction()
|
finalprotected |
Triggers the event {{static::EVENT_ON_BEFORE_ACTION}}
- См. также
- BitrixMainEngineController::getFullEventName. This method is invoked right before an action is executed. In case the action should not run, event handler have to return EvenResult with type EventResult::ERROR.
- Аргументы
- Возвращает
- bool
См. определение в файле controller.php строка 531
532 {
533 $event = new Event(
534 ‘main’,
535 static::getFullEventName(static::EVENT_ON_BEFORE_ACTION),
536 array(
537 ‘action’ => $action,
538 ‘controller’ => $this,
539 )
540 );
541 $event->send($this);
542
543 $allow = true;
544 foreach ($event->getResults() as $eventResult)
545 {
546 if ($eventResult->getType() != EventResult::SUCCESS)
547 {
548 $handler = $eventResult->getHandler();
549 if ($handler && $handler instanceof Errorable)
550 {
551 $this->errorCollection->add($handler->getErrors());
552 }
553
554 $allow = false;
555 }
556 }
557
558 return $allow;
559 }
◆ writeToLogException()
|
protected |
См. определение в файле controller.php строка 429
430 {
432 $exceptionHandler->writeToLog($e);
433 }
◆ $configurator
◆ $errorCollection
◆ $request
◆ ERROR_REQUIRED_PARAMETER
const ERROR_REQUIRED_PARAMETER = ‘MAIN_CONTROLLER_22001’
◆ ERROR_UNKNOWN_ACTION
const ERROR_UNKNOWN_ACTION = ‘MAIN_CONTROLLER_22002’
◆ EVENT_ON_AFTER_ACTION
const EVENT_ON_AFTER_ACTION = ‘onAfterAction’
◆ EVENT_ON_BEFORE_ACTION
const EVENT_ON_BEFORE_ACTION = ‘onBeforeAction’
◆ EXCEPTION_UNKNOWN_ACTION
const EXCEPTION_UNKNOWN_ACTION = 22002
◆ METHOD_ACTION_SUFFIX
const METHOD_ACTION_SUFFIX = ‘Action’ |
inherited |
◆ SCOPE_AJAX
const SCOPE_AJAX = ‘ajax’
◆ SCOPE_CLI
◆ SCOPE_REST
const SCOPE_REST = ‘rest’
Объявления и описания членов класса находятся в файле:
- C:/Bitrix/modules/main/lib/engine/controller.php
Views: 1254
Last Modified: 18.01.2022
You can use the following two approaches for processing AJAX queries in component:
class.php
Query handler in component class (class.php file) allows to:
- Incapsulate complete code in a single class
- Repeatedly use component methods, data and parameters
- Use component phrases and templates
- Re-define standard behaviour in descendant-components
For the component class to process queries, it’s necessary to:
- Implement the interface BitrixMainEngineContractControllerable
- Define action method with suffix Action
- Implement the method configureActions (usually returns an empty array === default configuration)
- When you need to add, process errors, you can implement BitrixMainErrorable
Note: Executing component in Ajax mode sequentially executes CBitrixComponent::onIncludeComponentLang, CBitrixComponent::onPrepareComponentParams and launching an action with filters.
Attention! Executing component in ajax-mode doesn’t launch method CBitrixComponent::executeComponent().
Example
<?php #components/bitrix/example/class.php use BitrixMainError; use BitrixMainErrorCollection; if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die(); class ExampleComponent extends CBitrixComponent implements BitrixMainEngineContractControllerable, BitrixMainErrorable { /** @var ErrorCollection */ protected $errorCollection; public function configureActions() { //when actions does not require configuration, write them as is. They will use default config return []; } public function onPrepareComponentParams($arParams) { $this->errorCollection = new ErrorCollection(); //parameter preparation //This code **will** be executed upon launching ajax-actions } public function executeComponent() { //Attention! This code **won't be** executed upon launching ajax-actions } //data from REQUEST will be inserted to parameter $person public function greetAction($person = 'guest') { return "Hi {$person}!"; } //example of error processing public function showMeYourErrorAction():? string { if (rand(3, 43) === 42) { $this->errorCollection[] = new Error('You are so beautiful or so handsome'); //now response will contain errors and automatic 'error' response status. return null; } return "Ok"; } /** * Getting array of errors. * @return Error[] */ public function getErrors() { return $this->errorCollection->toArray(); } /** * Getting once error with the necessary code. * @param string $code Code of error. * @return Error */ public function getErrorByCode($code) { return $this->errorCollection->getErrorByCode($code); } }
ajax.php
Query controller handler in the ajax.php file allows creating lightweight ajax-query handler, by directly dedicating logic from component.
To implement this:
- Create ajax.php file in the component root
- Define action method with Action suffix
The controller logic is fully the same as module controller description.
<?php #components/bitrix/example/ajax.php if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die(); class ExampleAjaxController extends BitrixMainEngineController { #data from REQUEST is automatically inserted into the parameter $person public function sayByeAction($person = 'guest') { return "Goodbye {$person}"; } public function listUsersAction(array $filter) { $users = []; //user list as per filter //array data composition for response return $users; } }
С версии битрикса 17.5.10 добавился новый функционал по работе с аяксом. Теперь можно вызвать аяксом произвольный метод модуля или произвольный метод компонента без необходимости выполнения шаблона сайта. То есть по запросу выполнится только немного служебного кода и собственно сам метод.
На клиенте все довольно просто. Мы должны отправить обычный аякс запрос по адресу /bitrix/services/main/ajax.php
В js библиотеке битрикса есть методы которые самостоятельно формируют запрос, запись получается слегка короче, чем при написании запроса другими способами. Я покажу примеры запросов на битриксовой библиотеке и на jquery
Содержимое статьи
- 1 Запрос к компоненту
- 1.1 Клиентская сторона
- 1.2 Серверная сторона:
- 2 Запрос к модулю:
- 2.1 Клиентская сторона
- 2.2 Серверная сторона
- 3 Выводы
На конференции битрикса в марте объяснялись нововведения в общих чертах, кому для понимания удобнее смотреть видео, вот:
На мой взгляд в виде текста такую информацию воспринимать удобнее. Поэтому я решил написать статью и раскрыть тему чуть более подробно
Запрос к компоненту
Для начала рассмотрим как происходит вызов методов компонента.
Клиентская сторона
Вот пример запроса на битриксовой библиотеке:
var request = BX.ajax.runComponentAction('custom:ajax', 'test', {
mode:'class',
data: {
param1: 'asd'
}
});
request.then(function(response){
console.log(response);
});
Приведу код и jsdoc битриксовой функции. Из jsdoc можно узнать параметры, которые эта функция ожидает
/**
*
* @param {string} component
* @param {string} action
* @param {Object} config
* @param {?string} [config.analyticsLabel]
* @param {?string} [config.signedParameters]
* @param {string} [config.method='POST']
* @param {string} [config.mode='ajax'] Ajax or class.
* @param {Object} [config.data]
* @param {?array} [config.headers]
* @param {?number} [config.timeout]
* @param {Object} [config.navigation]
*/
BX.ajax.runComponentAction = function (component, action, config)
{
config = prepareAjaxConfig(config);
config.mode = config.mode || 'ajax';
var getParameters = prepareAjaxGetParameters(config);
getParameters.c = component;
getParameters.action = action;
var url = BX.util.add_url_param('/bitrix/services/main/ajax.php', getParameters);
return buildAjaxPromiseToRestoreCsrf({
method: config.method,
dataType: 'json',
url: url,
data: config.data,
timeout: config.timeout,
preparePost: config.preparePost,
headers: config.headers
});
};
Вот примерно то же самое на jquery:
var query = {
c: 'custom:ajax',
action: 'test',
mode: 'class'
};
var data = {
param1: 'eee',
SITE_ID: 's1',
//sessid: BX.message('bitrix_sessid')
};
var request = $.ajax({
url: '/bitrix/services/main/ajax.php?' + $.param(query, true),
method: 'POST',
data: data
});
request.done(function (response) {
console.log(response);
});
Немного сложностей вносит использование csrf. Если кратко, то это защита от вызова метода на сторонних сайтах. Если вы хотите защитить запрос, то вам придется воспользоваться методом BX.message(‘bitrix_sessid’) в js (он закомментирован в объекте data). Либо каким то другим образом передать с бэкенда на фронт данные функции <?echo bitrix_sessid();?> и подставить эту строку в sessid.
Если же вы никакие сколько нибудь значимые данные в запросах не передаете, то проверку csrf можно будет отключить на бэкенде (для каждого аякс метода независимо, для каких-то можно и оставить) и отправлять запросы без sessid.
Серверная сторона:
На серверной стороне генерировать ответ будет обычный компонент. Так как в примере выше мы отправляли запросы к компоненту custom:ajax, то фактически будет выполняться метод testAction в классе из файла /local/components/custom/ajax/class.php, название класса в файле как обычно не важно. Вот примерно такое там может быть:
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
use BitrixMainEngineContractControllerable;
use BitrixMainEngineActionFilter;
class CCustomAjax extends CBitrixComponent implements Controllerable
{
/**
* @return array
*/
public function configureActions()
{
return [
'test' => [
'prefilters' => [
new ActionFilterAuthentication(),
new ActionFilterHttpMethod(
array(ActionFilterHttpMethod::METHOD_GET, ActionFilterHttpMethod::METHOD_POST)
),
new ActionFilterCsrf(),
],
'postfilters' => []
]
];
}
function executeComponent()
{
}
/**
* @param string $param2
* @param string $param1
* @return array
*/
public function testAction($param2 = 'qwe', $param1 = '')
{
return [
'asd' => $param1,
'count' => 200
];
}
}
Обращаю ваше внимание, на клиенте мы спрашиваем просто test, а по факту выполняется метод testAction.
Метод configureActions описывает пре- и постфильтры для запроса. В примере выше, чтобы запрос выполнился пользователь должен быть авторизован (префильтр ActionFilterAuthentication()), запрос должен идти методом GET или POST (префильтр ActionFilterHttpMethod) и если это POST запрос, то в запросе должна быть переменная sessid (префильтр ActionFilterCsrf). Это набор дефолтных префильтров. Если метод configureActions() ничего не будет возвращать, то именно эти префильтры по дефолту подставит битрикс. Если же метод возвращает массив, в одном из ключей которого есть описание метода test, то выполнятся именно пользовательские префильтры, а стандартные будут проигнорированы.
Выше я говорил, что можно убрать проверку csrf токена на бэкенде, это делается именно тут, в методе configureActions(). Чтобы csrf токен не проверялся нужно убрать ActionFilterHttpMethod и ActionFilterCsrf из возвращаемого массива для конкретного ajax метода, недостаточно убрать просто ActionFilterCsrf, если в ActionFilterHttpMethod разрешен POST, то префильтр ActionFilterCsrf автоматически включается.
На что еще обратить внимание. В методе testAction есть два аргумента — $param2 и $param1. Я намеренно поставил их в обратном порядке, чтобы показать как происходит маппинг данных из запроса. Если вы вернетесь в описание клиентской части, то увидите, что там передается param1, а param2 не передается. Битрикс проверяет имена переменных через php reflection и передает в аргументы метода ajax данные с тем же типом и названием. То есть другими словами не нужно задумываться какой у вас там аргумент первый, какой второй, как это все придет из браузера, в каком порядке, достаточно просто называть переменные одинаково на сервере и на клиенте.
В клиентском запросе мы указывали mode: ‘class’, если посмотреть jsdoc битриксовой функции, то оказывается, что она принимает еще и mode: ‘ajax’. На самом деле это практически то же самое, только подключается не class.php из папка компонента, а файл ajax.php. Ну и сам класс нужно наследовать немного иначе. Вот пример класса в файле ajax.php:
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
use BitrixMainEngineController;
class CustomAjaxController extends Controller
{
/**
* @return array
*/
public function configureActions()
{
return [
'test' => [
'prefilters' => []
]
];
}
/**
* @param string $param2
* @param string $param1
* @return array
*/
public static function testAction($param2 = 'qwe', $param1 = '')
{
return [
'asd' => $param1,
'count' => 300
];
}
}
То есть примерно то же самое, только убрали implements Controllerable, и отнаследовались от Controller.В остальном никаких отличий нет. Название класса так же как и с компонентом не важно.
Запрос к модулю:
Теперь попробуем отправлять запрос не к компоненту а к модулю. Я буду отправлять запросы к модулю local.lib.
Клиентская сторона
var request = BX.ajax.runAction('local:lib.api.test.example', {
data: {
param1: 'hhh'
}
});
request.then(function(response){
console.log(response);
});
Вот опять же код и jsdoc вызываемой функции
/**
*
* @param {string} action
* @param {Object} config
* @param {?string} [config.analyticsLabel]
* @param {string} [config.method='POST']
* @param {Object} [config.data]
* @param {?Object} [config.headers]
* @param {?Object} [config.timeout]
* @param {Object} [config.navigation]
* @param {number} [config.navigation.page]
*/
BX.ajax.runAction = function(action, config)
{
config = prepareAjaxConfig(config);
var getParameters = prepareAjaxGetParameters(config);
getParameters.action = action;
var url = BX.util.add_url_param('/bitrix/services/main/ajax.php', getParameters);
return buildAjaxPromiseToRestoreCsrf({
method: config.method,
dataType: 'json',
url: url,
data: config.data,
timeout: config.timeout,
preparePost: config.preparePost,
headers: config.headers
});
};
Аналогичный запрос на jquery:
var query = {
action: 'local:lib.api.test.example',
};
var data = {
param1: 'eee',
SITE_ID: 's1',
//sessid: BX.message('bitrix_sessid')
};
var request = $.ajax({
url: '/bitrix/services/main/ajax.php?' + $.param(query),
method: 'POST',
data: data
});
request.done(function (response) {
console.log(response);
});
Принципы формирования sessid те же, что и для компонента.
Серверная сторона
На серверной стороне есть отличия. Для начала в папке с модулем нам понадобится файл .settings.php, именно в папке с модулем. Вот пример файла:
<?php
return [
'controllers' => [
'value' => [
'namespaces' => [
'\Local\Lib\Controller' => 'api'
]
],
'readonly' => true
]
];
В этом файле мы регистрируем целый неймспейс. Или другими словами регистрируем целую папку, вся папка будет содержать контроллеры для работы с аяксом.
Далее создаем папку lib/controller и файл test.php в этой папке:
<?php
namespace LocalLibController;
use BitrixMainEngineController;
class Test extends Controller
{
/**
* @return array
*/
public function configureActions()
{
return [
'example' => [
'prefilters' => []
]
];
}
/**
* @param string $param2
* @param string $param1
* @return array
*/
public static function exampleAction($param2 = 'qwe', $param1 = '')
{
return [
'asd' => $param1,
'count' => 300
];
}
}
Содержимое тут примерно такое же как и в файле ajax.php компонента, так что останавливаться на этом не буду.
Ключевое для понимания работы аякс в модуле — как именно формируется action и как в итоге будет обрабатываться запрос. Разберем тестовый экшен local:lib.api.test.example
- local:lib — модуль local.lib, где точку заменили на двоеточие
- api — неймспейс который мы зарегистрировали в .settings.php модуля
- test — название файла и класса в папке lib/controller
- example — метод exampleAction в классе Test, без суффикса action
Каждый отдельный файл регистрировать в .settings.php модуля не нужно.
Выводы
В целом пока что впечатления от нового механизма положительные. Нужно еще оценить удобство на практике, но чувствуется, что над функционалом довольно долго думали, проектировали. Много нового функционала. Я не затронул еще одну интересную штуку про которую рассказывали на видео — механизм auto wiring (автоматическое связывание), простых примеров так сразу не смог придумать.
Пара идей куда это все эти новинки применить уже есть. Возможно после использования на практике я дополню статью. Пока что вся статья результат теоретического изучения в течение одного дня.
Если информация оказалась полезной, скажите спасибо, это мотивирует писать чаще 🙂
Пытаюсь использовать контроллеры битрикса для ajax запроса к своему модулю через BX.ajax.runAction.
В корне модуля лежит .settings.php со следующим содержимым:
return [
'controllers' => [
'value' => [
'namespaces' => [
'\Evsyukov\Main\Controller' => 'api',
],
],
'readonly' => true,
],
];
название модуля ‘testmodules’ далее /lib/controller/test.php
его код
namespace EvsyukovMainController;
class Book extends Controller {
public function configureActions()
{
return [
'greet' => [
'prefilters' => [
]
]
];
}
public function addBookAction()
{
$context = Application::getInstance()->getContext();
$request = $context->getRequest();
$arRequest = $request->getPostList()->toArray();
$this->addbook($arRequest);
}
}
пытаюсь вызвать так
BX.ajax.runAction(‘evsyukov:main.api.book.addbook’, {}
задан 5 авг 2022 в 19:32
В configureActions()
у вас ключ greet
, а метод назвали addBook
, вот так должно быть. Также метод должен возвращать массив.
<?php
namespace EvsyukovMainController;
use BitrixMainApplication;
use BitrixMainEngineController;
use BitrixMainEngineActionFilter;
class Book extends Controller {
public function configureActions()
{
return [
'addBook' => [
'prefilters' => [
new ActionFilterAuthentication(),
new ActionFilterHttpMethod(
[
ActionFilterHttpMethod::METHOD_GET,
ActionFilterHttpMethod::METHOD_POST,
]
),
//new ActionFilterCsrf(),
],
'postfilters' => []
]
];
}
public function addBookAction()
{
$context = Application::getInstance()->getContext();
$request = $context->getRequest();
$arRequest = $request->getPostList()->toArray();
return $arRequest;
}
}
ответ дан 8 авг 2022 в 12:31
Firsov36Firsov36
8941 золотой знак3 серебряных знака11 бронзовых знаков
2
Если название модуля не содержит «.», то битрикс считает, что модуль его и ищет среди своих модулей в namespace Bitrix. Чтобы для вашего модуля работал механизм контроллеров, имя модуля должно быть evsyukov.main
И в .settings.php надо добавить defaultNamespace:
return [
'controllers' => [
'value' => [
'defaultNamespace' => '\Evsyukov\Main\Controller',
'namespaces' => [
'\Evsyukov\Main\Controller' => 'api',
],
],
'readonly' => true,
],
ответ дан 9 авг 2022 в 12:57
Виктор КаревВиктор Карев
6754 серебряных знака9 бронзовых знаков
Содержание
Mysql query error: (1227)
Ошибка
Mysql query error: (1227) Access denied; you need (at least one of) the SYSTEM_VARIABLES_ADMIN or SESSION_VARIABLES_ADMIN privilege(s) for this operation (400)
Решение
GRANT SESSION_VARIABLES_ADMIN ON *.* TO 'bitrix_user'@'localhost';
Ошибка в типе содержимого
ERR_CONTENT_DECODING_FAILED
Добавить в htaccess
zlib.output_compression = on
или добавить в bitrix/php_interface/init.php
(или dbconf.php
)
define('BX_COMPRESSION_DISABLED',true);
Ошибка сокетов в site_checker
Причин много (корявый hosts файл, нет модуля sockets итд).
Но оказывается всё из-за того, что на /bitrix/admin/
установлен htpasswd (логин/пароль).
Браво Битрикс! Райская система. Всем рекомендую.
Не меняется memory_limit
Ещё одна дикая проблема Битрикса. Тому кто это придумал надо пожать руку, а потом оторвать её.
Не меняется memory_limit для PHP. Меняю в php.ini, меняю в htaccess, меняю где только можно, а результата нет. Смотрю phpinfo там правильный и master value и local value.
Не первый день в айти. Надо просто копнуть глубже
# grep -r --include=*.php "memory_limit" /var/www/foobar.com/
Какой-то сверх мозг в этой шайтан конторе решил, что будет весело прибить memory_limit гвоздями, потом полить супер-клеем и ещё сверху залить бетоном всё это дело. Ха-ха-ха, !@#$% смешно.
/var/www/foobar/bitrix/modules/main/admin/site_checker.php: @ini_set("memory_limit", "512M"); /var/www/foobar/bitrix/modules/main/classes/general/file.php: $memoryLimit = CUtil::Unformat(ini_get('memory_limit')); /var/www/foobar/bitrix/modules/main/classes/general/site_checker.php: array('check_memory_limit' => GetMessage('SC_T_MEMORY')), /var/www/foobar/bitrix/modules/main/classes/general/site_checker.php: function check_memory_limit() /var/www/foobar/bitrix/modules/main/classes/general/site_checker.php: $cur = ini_get('memory_limit'); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $memoryLimit = WelcomeStep::unformat(ini_get('memory_limit')); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $memoryLimit = WelcomeStep::unformat(get_cfg_var('memory_limit')); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: @ini_set("memory_limit", "64M"); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $memoryLimit = WelcomeStep::unformat(ini_get('memory_limit')); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: '.($memoryLimit > 0 && $memoryLimit < $this->memoryMin*1048576 ? $this->ShowResult(ini_get('memory_limit'), "ERROR") : $this->ShowResult(ini_get('memory_limit'), "OK")).' /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $fileContent .= "n@ini_set("memory_limit", "1024M");n"; /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $memoryLimit = WelcomeStep::unformat(ini_get('memory_limit')); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $memoryLimit = WelcomeStep::unformat(get_cfg_var('memory_limit')); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: @ini_set("memory_limit", "512M"); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $memoryLimit = WelcomeStep::unformat(ini_get('memory_limit')); /var/www/foobar/bitrix/modules/main/install/wizard/wizard.php: $fileContent .= "n@ini_set("memory_limit", "512M");n"; /var/www/foobar/bitrix/modules/main/lang/en/admin/site_checker.php:PHP defines the memory limit in php.ini by setting the <b>memory_limit</b> parameter. However, this may be overridden on shared hostings. You should not trust this parameter. /var/www/foobar/bitrix/modules/main/lang/en/admin/site_checker.php:The test attempts to increase the value of <b>memory_limit</b> using the code: /var/www/foobar/bitrix/modules/main/lang/en/admin/site_checker.php:<code>ini_set("memory_limit", "512M") /var/www/foobar/bitrix/modules/main/lang/en/admin/site_checker.php:$MESS["SC_MEMORY_CHANGED"] = "The value of memory_limit was increased from #VAL0# to #VAL1# using ini_set while testing."; /var/www/foobar/bitrix/modules/main/lang/ru/admin/site_checker.php:$MESS["SC_MEMORY_CHANGED"] = "Значение memory_limit было увеличено с #VAL0# до #VAL1# на момент тестирования через ini_set."; /var/www/foobar/bitrix/modules/main/lang/ru/admin/site_checker.php:Основной параметр ограничения памяти в php.ini - это <b>memory_limit</b>. Но доверять значению параметра нельзя, т.к. на хостингах могут быть установлены дополнительные ограничения памяти. /var/www/foobar/bitrix/modules/main/lang/ru/admin/site_checker.php:Обратите внимание, тест пытается увеличить значение <b>memory_limit</b> используя код: /var/www/foobar/bitrix/modules/main/lang/ru/admin/site_checker.php:<code>ini_set("memory_limit", "512M") /var/www/foobar/bitrix/php_interface/dbconn.php:@ini_set("memory_limit", "128M"); /var/www/foobar/cli/parse_list.php:ini_set('memory_limit', '2048M'); /var/www/foobar/local/php_interface/dbconn.example.php:@ini_set("memory_limit", "128M");
Вот почему у меня парсер в админке не работал. Из-за 128M memory_limit. Даже не знаю какой мемчик бы подошёл для этой ситуации лучше — Друзь с его вы там ох!@#$% или Винни Пух ныряющий в болото. Битрикс💩, а WordPress, Joomla, Drupal по сравнению с этим платным💩 просто топчик.
DB query error. Please try later
В файле /bitrix/php_interface/dbconn.php
установить
$DBDebug = true;
Обновляем страницу сайта, чтобы увидеть в чем проблема.
The script encountered an error
The script encountered an error and will be aborted. To view extended error messages, enable this feature in .settings.php.
Установить ‘debug’ ⇒ true в bitrix/.settings.php
(в старых версиях bitrix/php_interface/dbconn.php
).
This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies