Claude Skill

bitrix-controllers

Engine Controller/JsonController: thin actions, filter attributes, CurrentUser, errors. Use for AJAX/REST/routed endpoints.

LLM Mart · 0 points · 12 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download bxmaximum-bitrix-framework-skills-skills_bitrix-controllers-66c40e0.zip · 7 KB
Part of bxmaximum/bitrix-framework-skills — 38 skills

Install

skills CLI npx skills add https://github.com/bxmaximum/bitrix-framework-skills/tree/main/skills/bitrix-controllers
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install bxmaximum-bitrix-framework-skills@llmmart
Git git clone https://github.com/bxmaximum/bitrix-framework-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole bxmaximum/bitrix-framework-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Bitrix Controllers

Baseline: main 23.0+. Features newer than baseline are marked Since.

Progressive disclosure: open only the rule files that match the task. Do not read every rules/*.md.

How to use

  1. Identify the layer the task touches.
  2. Open the matching rules/*.md below.
  3. Prefer framework-native Bitrix patterns over custom abstractions.

Choose a rule file

When to read rules/basics.md

Read rules/basics.md (Location, thin controller, autowire) when the task involves:

  • Location and Naming
  • Minimal Controller
  • Action Parameter Autowiring
  • Controller Lifecycle
  • Additional Autowire Types
  • Front-end Call

When to read rules/filters.md

Read rules/filters.md (Filters and attributes) when the task involves:

  • Default Prefilters
  • Action Filters
  • PHP 8 Attribute Filters (preferred)
  • #[ActionAccess] / AccessCheck (module ACL)

When to read rules/errors-response.md

Read rules/errors-response.md (Errors, responses, scope) when the task involves:

  • Errors
  • Response Types
  • Scope (AJAX / REST / CLI)
  • Checklist

Cross-cutting invariants (apply regardless of rule file)

  • Never configure the same action both via filter attributes and configureActions() — the controller fails with Invalid configuration of actions.
  • Do not register one controller both as an HTTP route target (/local/routes/web.php) and in the AJAX controllers.defaultNamespace of .settings.php. Keep separate Web\* and Ajax\* controllers, each with its own getAutoWiredParameters().
  • Rendering helpers renderView() / renderComponent() / renderExtension() — Since main 25.700.0. They return HTML (HttpResponse-based) and are for HTTP routes only; BX.ajax.runAction() expects JSON. For AJAX use renderComponentAjax() — JSON with html, assets, additionalParams, componentResult. renderExtension() / renderView() / renderComponent() accept withSiteTemplate: false to skip the site template. renderExtension() requires controllerEntrypoint in the extension's config.php; it renders in the browser (not SSR).
  • PageNavigation autowire (global, nav id nav) accepts a page size only within 1–50 (setPageSizes(range(1, 50))); an out-of-range size is silently ignored and the default 20 is used.
  • Request DTOs are wired with new ValidationParameter(...) in getAutoWiredParameters() — not a #[ValidationParameter] attribute (that class does not exist).
  • #[ActionAccess] requires the controller to implement AccessCheckControllerInterface. Without it the engine throws SystemException.

Checklist

  • Opened only the rule file(s) needed for this task.
  • Followed DI / /local/ / security canons from AGENTS.md.
  • Respected the cross-cutting invariants above (no attribute + configureActions() mix, separate Web/Ajax controllers).
Files (bitrix-framework-skills)
  • rules
    • basics.md 5.2 KB
      # Location, thin controller, autowire
      
      ## Location and Naming
      
      - Files: `/local/modules/<vendor>.<module>/lib/Infrastructure/Controller/<Name>.php`.
      - Namespace (default): `\Vendor\Module\Infrastructure\Controller\<Name>`.
      - Public URL for AJAX: `/bitrix/services/main/ajax.php?action=vendor:module.<name>.<action>`.
      - URL can be rewritten by a route (see `bitrix-routing`).
      
      Namespace configuration — in `/local/modules/vendor.module/.settings.php`:
      
      ```php
      'controllers' => [
          'value' => [
              'defaultNamespace' => '\\Vendor\\Module\\Infrastructure\\Controller',
              'namespaces' => [
                  '\\Vendor\\Module\\Infrastructure\\Controller\\Web' => 'web',
              ],
              'restIntegration' => ['enabled' => true], // for REST
          ],
          'readonly' => true,
      ],
      ```
      
      Access to `Web\PostController::getAction` → `?action=vendor:module.web.post.get`.
      
      ## Minimal Controller
      
      `ControllerBuilder` builds the controller with `Request` only (`newInstance($request)`). Do **not** put custom services in the controller constructor — inject them via **action method parameters** (autowire). Avoid manual `new Service()` / ServiceLocator lookups inside actions when autowire works.
      
      Keep `*Action()` as **orchestration only**: accept input → call application service → map `Result` / errors → return data or response helper. No fat business logic, heavy transforms, or hidden side effects in the controller.
      
      Prefer **PHP 8 attributes** for filters. Use `configureActions()` only for compatibility or cases attributes cannot express.
      
      ```php
      <?php declare(strict_types=1);
      
      namespace Vendor\Module\Infrastructure\Controller;
      
      use Bitrix\Main\Engine\Controller;
      use Bitrix\Main\Engine\ActionFilter;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Authentication;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\HttpMethod;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\DisablePrefilters;
      use Bitrix\Main\Engine\CurrentUser;
      use Bitrix\Main\Error;
      use Vendor\Module\Application\Service\PostService;
      
      final class Post extends Controller
      {
          #[HttpMethod([HttpMethod::METHOD_GET])]
          #[DisablePrefilters([ActionFilter\Csrf::class])]
          public function getAction(int $id, PostService $postService): array
          {
              $post = $postService->find($id);
              if ($post === null)
              {
                  $this->addError(new Error('Not found', 'POST_NOT_FOUND'));
                  return [];
              }
      
              return ['post' => $post];
          }
      
          #[Authentication]
          #[HttpMethod([HttpMethod::METHOD_POST])]
          public function createAction(
              string $title,
              string $body,
              PostService $postService,
              CurrentUser $currentUser,
          ): array
          {
              $result = $postService->create($title, $body, (int)$currentUser->getId());
      
              if (!$result->isSuccess())
              {
                  $this->addErrors($result->getErrors());
                  return [];
              }
      
              return $result->getData();
          }
      }
      ```
      
      ### `JsonController`
      
      `Bitrix\Main\Engine\JsonController` extends `Controller` and adds `ContentType([JSON])` to default prefilters. Use it for JSON-body APIs; otherwise prefer plain `Controller`.
      
      ## Action Parameter Autowiring
      
      Action parameters are collected by the engine in the following order:
      
      1. **Scalar types** (`int`, `string`, `bool`, `float`, `array`) → from `GET`/`POST`/`FILES`.
      2. **Service objects** → from `ServiceLocator` by name/type.
      3. **`HttpRequest`, `Session`, `CurrentUser`** → from context.
      4. **Request DTO** via `Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter` in `getAutoWiredParameters()` → mapping + validation (see `bitrix-validation`). This is an AutoWire `Parameter` subclass, **not** a PHP attribute.
      5. **ORM objects**, if the action accepts `EntityObject` — loaded by `id`.
      
      Missing mandatory parameter → automatic error.
      
      For a cohesive input set (validation, nested structure, pagination), prefer a Request DTO over a long list of scalars.
      
      ### Current user
      
      - Prefer `CurrentUser $user` in the action signature, or `$this->getCurrentUser()`.
      - Do **not** use global `$USER` inside new controller code.
      
      ## Controller Lifecycle
      
      1. Constructor — engine passes `Request` only (`ControllerBuilder`). No ServiceLocator DI for custom services.
      2. `init()` — called from the constructor; load modules if needed (`parent::init()` first when overriding). Prefer action-parameter autowire over resolving services in `init()`.
      3. Prefilters run.
      4. Action method executes.
      5. Postfilters run.
      6. Response is serialized.
      
      Do not hide action logic in `init()`, `__construct()`, `processBeforeAction()`, or `processAfterAction()`.
      
      `executeComponent()` in component controllers does **not** run during AJAX actions — use `onPrepareComponentParams()` for shared setup.
      
      ## Additional Autowire Types
      
      - `Bitrix\Main\Engine\CurrentUser` — current user context.
      - `Bitrix\Main\Engine\JsonPayload` — raw JSON body.
      - `Bitrix\Main\UI\PageNavigation` — pagination from request.
      
      Custom DTO autowiring via `getAutoWiredParameters()`.
      
      ## Front-end Call
      
      ```js
      BX.ajax.runAction('vendor:module.post.create', {
          data: { title: 'Title', body: 'Body' },
      }).then((response) => {
          console.log(response.data);
      });
      ```
      
      For REST — `BX.rest.callMethod('vendor.module.post.create', {...})`.
      
    • errors-response.md 3.1 KB
      # Errors, responses, scope
      
      ## Errors
      
      - `$this->addError(new \Bitrix\Main\Error('msg', 'CODE', ['key' => 'value']));`
      - `$this->addErrors($result->getErrors());`
      - Never throw exceptions outward for ordinary user errors — use `Result` + `Error` (see `bitrix-result-and-errors`).
      - Response with errors automatically receives `status: 'error'` and `errors` array.
      - When returning success data from a service `Result`, prefer a narrow `getData()` contract — do not leak internal structures.
      
      ## Response Types
      
      - `array` → JSON: `{ "status": "success", "data": [...] }`.
      - `null` → `{ "status": "success" }` without data.
      - `Bitrix\Main\HttpResponse` — custom response (headers, status, body).
      - `Bitrix\Main\Engine\Response\HtmlContent` — AJAX JSON with `html` + `assets` (extends `AjaxJson`).
      - `Bitrix\Main\Engine\Response\Json` / `Redirect` / `AjaxJson`.
      - `Bitrix\Main\Engine\Response\Render\View` / `Render\Component` / `Render\Extension` — HTML for HTTP routes (`renderView` / `renderComponent` / `renderExtension`).
      - `Bitrix\Main\Engine\Response\Component` — JSON component payload from `renderComponentAjax()` (not the same class as `Render\Component`).
      - `Bitrix\Main\Engine\Response\BFile` / `File` — file delivery (`BFile::createByFileId()` for `b_file`).
      - `Bitrix\Main\Engine\Response\ResizedImage` — resized image (`createByImageId($id, $w, $h)`); never take width/height raw from the request.
      - `Bitrix\Main\Engine\Response\Zip\Archive` — ZIP stream.
      - `Bitrix\Main\Engine\Response\OpenDesktopApp` / `OpenMobileApp` — deep-link into native Bitrix apps.
      
      Controller helpers:
      
      ```php
      return $this->renderView('list', ['items' => $items]);
      // => /local/modules/vendor.module/views/list.php
      
      return $this->renderComponent('vendor:post.list', '.default', ['IBLOCK_ID' => 12]);
      
      return $this->renderExtension('vendor.post.list', ['items' => $items]);
      
      return $this->redirectTo('/posts/');
      ```
      
      Prefer these helpers / typed responses over manual `header()` / `json_encode()` (see `bitrix-request-response`).
      
      ## Scope (AJAX / REST / CLI)
      
      - **AJAX**: `/bitrix/services/main/ajax.php?action=...` or `BX.ajax.runAction('...', {})`. Available when controller is declared and `controllers` exists in `.settings.php`.
      - **REST**: requires `restIntegration.enabled = true` + `rest` module.
      - **CLI**: possible with `ActionFilter\Scope` when calling controllers from commands.
      
      Different scopes need different filter sets. CSRF does not apply to REST by default — add an explicit strategy.
      
      ## Checklist
      
      - [ ] Controller is thin: orchestration only; business logic in a service.
      - [ ] Filters use attributes by default; `configureActions` only when needed.
      - [ ] `getDefaultPreFilters()` extends parent when overridden.
      - [ ] Current user via `CurrentUser` / `getCurrentUser()`, not global `$USER`.
      - [ ] Dependencies via **action parameters**, not controller constructor.
      - [ ] Input via Request DTO + `ValidationParameter` in `getAutoWiredParameters()` when the contract is non-trivial.
      - [ ] Errors via `$this->addError` / `addErrors`, not exceptions for normal failures.
      - [ ] Return type explicit: `array`, `HttpResponse`, or `renderXxx` / `redirectTo`.
      
    • filters.md 5 KB
      # Filters and attributes
      
      ## Default Prefilters
      
      By default, actions get: `Authentication` + `HttpMethod([GET, POST])` + `Csrf`.
      
      When overriding `getDefaultPreFilters()`, **extend** `parent::getDefaultPreFilters()` — do not rebuild the base protection from scratch without reason.
      
      ## Action Filters
      
      Predefined filters:
      
      - `ActionFilter\Authentication` — requires an authorized user (401 without redirect).
      - `ActionFilter\Csrf` — `sessid`/`X-Bitrix-Csrf-Token` check. **Limited to `SCOPE_AJAX`** (`listAllowedScopes()`); does not run for REST/CLI scopes.
      - `ActionFilter\HttpMethod([...])` — method restriction.
      - `ActionFilter\CloseSession` — closes session before action (parallel AJAX).
      - `ActionFilter\ContentType(['application/json'])` — allowed `Content-Type`.
      - `ActionFilter\Scope($scope)` — restricts call to a specific scope (ajax/rest/cli).
      - `ActionFilter\Cors` — CORS headers for cross-origin AJAX.
      - `ActionFilter\AccessCheck` / `#[ActionAccess]` — module ACL via `AccessibleController` (see below).
      - `ActionFilter\Token` — signed entity token in request headers (`X-Bitrix-Sign-Entity` / `X-Bitrix-Sign-Token` via `ActionFilter\Service\Token`). Niche; prefer CSRF + rights for ordinary AJAX.
      
      If a built-in filter is missing, write a custom `ActionFilter\Base` — do not copy-paste checks into every action.
      
      ## `#[ActionAccess]` (module ACL)
      
      When the module already has an `access` controller (`Bitrix\Main\Access\AccessibleController` / `BaseAccessController`), check the action with `#[ActionAccess]` instead of a hand-rolled rights filter.
      
      The controller **must** implement `Bitrix\Main\Engine\Contract\AccessCheckControllerInterface` and return the access controller from `getAccessController()`. Otherwise `AccessCheck` throws `SystemException`.
      
      Default strategy is `ItemIdFromRequestStrategy`: it reads a request key (default `id`) and calls `checkByItemId($action, $itemId)`. Override the key with `strategyArgs`.
      
      ```php
      use Bitrix\Main\Access\AccessibleController;
      use Bitrix\Main\Engine\ActionFilter\Access\ItemIdFromRequestStrategy;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Access\ActionAccess;
      use Bitrix\Main\Engine\Contract\AccessCheckControllerInterface;
      use Bitrix\Main\Engine\CurrentUser;
      
      final class Post extends Controller implements AccessCheckControllerInterface
      {
          public function getAccessController(): AccessibleController
          {
              return PostAccessController::getInstance((int)CurrentUser::get()->getId());
          }
      
          #[ActionAccess(
              action: PostActionDictionary::VIEW, // string or UnitEnum (BackedEnum → value, else → name)
              strategy: ItemIdFromRequestStrategy::class,
              strategyArgs: ['itemIdRequestKey' => 'id'],
          )]
          public function getAction(int $id, PostService $postService): array
          {
              // ...
          }
      }
      ```
      
      `#[ActionAccess]` does **not** set HTTP 403 by default (`setHttpStatus` is only on `AccessCheck` itself, default `false`). The action is rejected with an engine error; set status yourself in a custom strategy/`AccessCheck` if the HTTP code matters.
      
      Custom strategies implement `AccessCheckStrategyInterface::create()` + `check()`. Do not use `#[ActionAccess]` unless the module actually has an access controller — for simple auth keep `Authentication` / a small `ActionFilter\Base`.
      
      ## PHP 8 Attribute Filters (preferred)
      
      ```php
      use Bitrix\Main\Engine\ActionFilter;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Prefilters;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\HttpMethod;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Authentication;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Csrf;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\EnablePrefilters;
      use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\DisablePrefilters;
      
      final class Post extends Controller
      {
          #[HttpMethod([HttpMethod::METHOD_GET])]
          #[DisablePrefilters([ActionFilter\Csrf::class])]
          public function listAction(): array { /* ... */ }
      
          #[Authentication]
          #[HttpMethod([HttpMethod::METHOD_POST])]
          #[Csrf]
          public function createAction(string $title): array { /* ... */ }
      
          #[DisablePrefilters([ActionFilter\Authentication::class, ActionFilter\Csrf::class])]
          #[EnablePrefilters([
              new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_GET]),
          ])]
          public function publicPingAction(): array
          {
              return ['ok' => true];
          }
      }
      ```
      
      Controller-level defaults via `getDefaultPreFilters()` / `getDefaultPostFilters()`. Use `#[EnablePrefilters]` / `#[DisablePrefilters]` to adjust inherited defaults per action.
      
      ### `configureActions()` (compatibility)
      
      Use when attributes are insufficient or when patching legacy controllers:
      
      ```php
      public function configureActions(): array
      {
          return [
              'get' => [
                  '+prefilters' => [new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_GET])],
                  '-prefilters' => [ActionFilter\Csrf::class],
              ],
          ];
      }
      ```
      
      Format keys: `prefilters` (replace), `+prefilters` (add), `-prefilters` (remove by FQCN), `postfilters`.
      
  • SKILL.md 3 KB
    ---
    name: bitrix-controllers
    description: "Engine Controller/JsonController: thin actions, filter attributes, CurrentUser, errors. Use for AJAX/REST/routed endpoints."
    ---
    
    # Bitrix Controllers
    
    Baseline: **main 23.0+**. Features newer than baseline are marked **Since**.
    
    Progressive disclosure: open **only** the rule files that match the task. Do not read every `rules/*.md`.
    
    ## How to use
    
    1. Identify the layer the task touches.
    2. Open the matching `rules/*.md` below.
    3. Prefer framework-native Bitrix patterns over custom abstractions.
    
    
    ## Choose a rule file
    
    ### When to read `rules/basics.md`
    
    Read `rules/basics.md` (`Location, thin controller, autowire`) when the task involves:
    
    - Location and Naming
    - Minimal Controller
    - Action Parameter Autowiring
    - Controller Lifecycle
    - Additional Autowire Types
    - Front-end Call
    
    ### When to read `rules/filters.md`
    
    Read `rules/filters.md` (`Filters and attributes`) when the task involves:
    
    - Default Prefilters
    - Action Filters
    - PHP 8 Attribute Filters (preferred)
    - `#[ActionAccess]` / `AccessCheck` (module ACL)
    
    ### When to read `rules/errors-response.md`
    
    Read `rules/errors-response.md` (`Errors, responses, scope`) when the task involves:
    
    - Errors
    - Response Types
    - Scope (AJAX / REST / CLI)
    - Checklist
    
    ## Cross-cutting invariants (apply regardless of rule file)
    
    - Never configure the same action **both** via filter attributes and `configureActions()` — the controller fails with `Invalid configuration of actions`.
    - Do not register one controller both as an HTTP route target (`/local/routes/web.php`) and in the AJAX `controllers.defaultNamespace` of `.settings.php`. Keep separate `Web\*` and `Ajax\*` controllers, each with its own `getAutoWiredParameters()`.
    - Rendering helpers `renderView()` / `renderComponent()` / `renderExtension()` — **Since main 25.700.0**. They return HTML (`HttpResponse`-based) and are for HTTP routes only; `BX.ajax.runAction()` expects JSON. For AJAX use `renderComponentAjax()` — JSON with `html`, `assets`, `additionalParams`, `componentResult`. `renderExtension()` / `renderView()` / `renderComponent()` accept `withSiteTemplate: false` to skip the site template. `renderExtension()` requires `controllerEntrypoint` in the extension's `config.php`; it renders in the browser (not SSR).
    - `PageNavigation` autowire (global, nav id `nav`) accepts a page size only within 1–50 (`setPageSizes(range(1, 50))`); an out-of-range `size` is silently ignored and the default 20 is used.
    - Request DTOs are wired with `new ValidationParameter(...)` in `getAutoWiredParameters()` — **not** a `#[ValidationParameter]` attribute (that class does not exist).
    - `#[ActionAccess]` requires the controller to implement `AccessCheckControllerInterface`. Without it the engine throws `SystemException`.
    
    ## Checklist
    
    - [ ] Opened only the rule file(s) needed for this task.
    - [ ] Followed DI / `/local/` / security canons from `AGENTS.md`.
    - [ ] Respected the cross-cutting invariants above (no attribute + `configureActions()` mix, separate Web/Ajax controllers).
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related