Дата последнего изменения: 15.04.2024
Контроллеры состоят из действий, которые являются основной сутью и их в конечном итоге запрашивает пользователь для получения результата. В одном контроллере может быть одно или несколько действий. Для примера сделаем контроллер с двумя действиями item.add и item.view в модуле example. В рамках примера контроллер располагается по пути /modules/vendor.example/lib/controller/item.php
.
Первый шаг - создать в корне модуля файл .settings.php
.
<?php //modules/vendor.example/.settings.php return [ 'controllers' => [ 'value' => [ 'defaultNamespace' => '\\Vendor\\Example\\Controller', ], 'readonly' => true, ] ];
Далее создаётся сам файл контроллера (смотри подробное описание доступных методов):
namespace Vendor\Example\Controller; use \Bitrix\Main\Error; class Item extends \Bitrix\Main\Engine\Controller { 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
.
$_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 \Bitrix\Main\Web\Uri $uri **/ $uri = \Bitrix\Main\Engine\UrlManager::getInstance()->create('vendor:example.Item.view', ['id' => 1]); echo $uri; // /bitrix/services/main/ajax.php?action=vendor:example.Item.view&id=1 // выполняем GET-запрос
Контроллеры должны быть унаследованы от \Bitrix\Main\Engine\Controller или его потомков. Контроллеры могут располагаться внутри модуля, либо внутри компонента в файле ajax.php и быть контроллером для компонента.
Создание действий, это создание просто методов в конкретном контроллере. Метод обязан быть public и иметь суффикс Action.
namespace Vendor\Example\Controller; class Item extends \Bitrix\Main\Engine\Controller { public function addAction(array $fields) { //... } }
Возвращаемое значение действия представляет собой данные ответа, которые будут высланы клиенту.
Если действие возвращает \Bitrix\Main\HttpResponse или его наследников, то данный объект и будет отправлен клиенту. Если действие возвращает некие данные, то они должны приводиться к скаляру или объекту, который после будет превращен в JSON и на основе него будет сформирован \Bitrix\Main\Engine\Response\AjaxJson
.
В целом действие может вернуть не просто скаляры, но и объекты, которые реализуют следующие интерфейсы:
\JsonSerializable
\Bitrix\Main\Type\Contract\Arrayable
\Bitrix\Main\Type\Contract\Jsonable
Либо конкретные наследники \Bitrix\Main\HttpResponse:
Есть возможность создавать классы-действия, которые унаследованы от \Bitrix\Main\Engine\Action
. Подобная возможность может потребоваться, когда необходимо повторно использовать логику в нескольких контроллерах. Например, если реализуется одинаковый протокол обмена в разных модулях (стандартный поиск, выполнение пошаговых действий с прогрессом и тому подобное.). Для использования нужно описать в карте конфигурации контроллера метод configureActions:
class Test extends \Bitrix\Main\Engine\Controller { public function configureActions() { return [ 'testoPresto' => [ 'class' => \TestAction::class, 'configure' => [ 'who' => 'Me', ], ], ]; } }
И вот сам TestAction:
<?php use \Bitrix\Main\Engine\Action; 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 создаёт контроллер на основе соглашения по именованию. Далее работу выполняет контроллер:
EventResult::SUCCESS
выполнение блокируется. На данном событии выполняются префильтры.Controller::processAfterAction(Action $action, $result)
.\Bitrix\Main\Engine\Response\AjaxJson
с этими данными, либо отправляет объект ответа от действия.Указание нескольких 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; }