Перейти к содержанию

Создание контроллера

Контроллер в Bitrix Framework — это PHP-класс, который обрабатывает AJAX-запросы от браузера. Он получает данные, выполняют бизнес-логику и формирует ответ.

В статье приведен пример, как в компонент my:user.card добавить функционал лайков. Для этого создадим контроллер, который будет:

  • принимать запросы на добавление и удаление лайков,

  • сохранять отметки в cookie-файлах,

  • возвращать текущее состояние.

Совет

Пример основан на модуле my.module и компоненте my:user.card, которые описаны в статьях:

Как работает вызов контроллера

Bitrix Framework связывает JavaScript-вызов и PHP-метод по имени — без дополнительной настройки. Это работает, когда соблюдаете три правила:

  • размещаете контроллер в папке /lib/controller/ модуля,

  • объявляете класс в правильном пространстве имен,

  • вызываете действие контроллера по шаблону vendor:module.ControllerName.actionName.

В примере модуль называется my.module. Bitrix Framework преобразует его имя в пространство имен. Первая часть my становится My, вторая moduleModule, итого: My\Module.

Создайте файл контроллера, например, /lib/controller/user.php со следующим содержимым:

namespace My\Module\Controller;

class User extends \Bitrix\Main\Engine\Controller
{
    public function likeAction()
    {
        // …
    }
}

Теперь можно вызвать метод из JavaScript:

BX.ajax.runAction('my:module.user.like', { /* данные */ });

Система разбирает имя действия по частям:

  • my — первая часть символьного кода модуля my.module,

  • module — вторая часть символьного кода,

  • user — имя класса контроллера User в нижнем регистре,

  • like — имя метода likeAction() без суффикса Action.

В результате система находит класс \My\Module\Controller\User и запускает метод likeAction().

Сопоставление работает, если в файле модуля .settings.php указан параметр defaultNamespace. Без этого Bitrix Framework не найдет класс и вернет ошибку.

Совет

Подробнее о правилах читайте в статье Контроллеры.

Структура модуля с контроллером

В структуре модуля my.module создайте файлы контроллера:

  • /lib/Services/LikeService.php — для класса-сервиса лайков,

  • /lib/controller/user.php — для описания контроллера.

/local/modules/my.module/
├── install/
│   ├── components/
│   │   └── my/
│   │       └── user.card/          // Компонент карточки пользователя
│   │           ├── .description.php
│   │           ├── .parameters.php
│   │           ├── class.php
│   │           ├── templates/
│   │           │   └── .default/
│   │           │       ├── template.php
│   │           │       ├── script.js
│   │           │       └── style.css
│   │           └── lang/
│   │               └── ru/
│   │                   └── messages.php
│   ├── index.php                   // Файл установки
│   └── version.php                 // Версия модуля
├── lib/
│   ├── controller/
│   │   └── user.php                // Контроллер для обработки лайков
│   └── Services/
│       └── LikeService.php         // Сервис для бизнес-логики
├── lang/
│   └── ru/
│       ├── install/
│       │   └── index.php           // Языковой файл установки
│       └── messages.php            // Общие языковые файлы
└── .settings.php                   // Настройка пространства имен контроллеров

Сервис для бизнес-логики

В файле /lib/Services/LikeService.php опишите класс LikeService для реализации логики работы с лайками. Чтобы обрабатывать лайки пользователей, добавьте в класс:

  • константу COOKIE_NAME — имя cookie для хранения ID пользователей, в чьих карточках поставлены лайки.

  • метод isLiked — проверяет, поставлен ли лайк в карточке пользователя с идентификатором userId.

  • метод likeUser — добавляет идентификатор пользователя в список пользователей, в чьих карточках поставлен лайк. Если пользователь уже в списке, он не будет добавлен повторно.

  • метод dislikeUser — удаляет идентификатор пользователя из списка пользователей, которым поставлен лайк.

  • метод getLikedUsersIds — извлекает и декодирует список идентификаторов пользователей из cookie.

  • метод setLikedUsersIds — кодирует массив идентификаторов пользователей в формат JSON и сохраняет его в cookie, которые будут храниться 30 дней.

<?php
namespace My\Module\Services;

use Bitrix\Main\ArgumentException;
use Bitrix\Main\Context;
use Bitrix\Main\Web\Cookie;
use Bitrix\Main\Web\Json;

class LikeService
{
    private const COOKIE_NAME = 'liked_users';

    public function isLiked(int $userId): bool
    {
        return in_array($userId, $this->getLikedUsersIds());
    }

    public function likeUser(int $userId): void
    {
        $likedUsers = $this->getLikedUsersIds();
        $likedUsers[] = $userId;

        $this->setLikedUsersIds(
            array_unique($likedUsers)
        );
    }

    public function dislikeUser(int $userId): void
    {
        $likedUsers = array_filter($this->getLikedUsersIds(), static fn($i) => (int)$i !== $userId);

        $this->setLikedUsersIds(
            array_unique($likedUsers)
        );
    }

    private function getLikedUsersIds(): array
    {
        try
        {
            $cookieValue = Context::getCurrent()->getRequest()->getCookie(self::COOKIE_NAME);
            if (empty($cookieValue))
            {
                return [];
            }

            $value = Json::decode($cookieValue);
            if (!is_array($value))
            {
                return [];
            }

            return $value;
        }
        catch (ArgumentException)
        {
            return [];
        }
    }

    private function setLikedUsersIds(array $likedUsers): void
    {
        Context::getCurrent()->getResponse()->addCookie(
            new Cookie(
                self::COOKIE_NAME,
                Json::encode($likedUsers),
                time() + 60 * 60 * 24 * 30 // 30 days
            )
        );
    }
}

Контроллер обработки лайков

В файле /lib/controller/user.php опишите контроллер User, который управляет действиями для обработки лайков.

Контроллер наследуется от \Bitrix\Main\Engine\Controller. Он не содержит бизнес-логику — только маршрутизацию, валидацию и вызов сервиса.

Добавьте два метода:

  • getDefaultPreFilters — определяет стандартные фильтры для всех действий,

  • likeAction — обрабатывает действие лайка пользователя.

<?php
namespace My\Module\Controller;

use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Error;
use My\Module\Services\LikeService;

class User extends Controller
{
    /**
     * Настройка фильтров для действий
     *
     * @return array
     */
    protected function getDefaultPreFilters()
    {
        return [
            // Разрешает запросы только авторизованным пользователям
            new ActionFilter\Authentication(),

            // Принимает только POST-запросы
            new ActionFilter\HttpMethod([
                ActionFilter\HttpMethod::METHOD_POST,
            ]),

            // Проверяет CSRF-токен — защищает от подделки запроса
            new ActionFilter\Csrf(),
        ];
    }

    /**
     * Действие для обработки лайков
     *
     * @param  LikeService $service
     * @param  int         $likedUserId
     *
     * @return array|null
     */
    public function likeAction(LikeService $service, int $likedUserId)
    {
        // Базовая валидация: ID должен быть положительным целым
        if ($likedUserId < 1)
        {
            $this->addError(new Error('Неверный ID пользователя'));

            return null;
        }

        // Если лайка нет — добавляем, иначе удаляем
        $isLikeAction = !$service->isLiked($likedUserId);
        if ($isLikeAction)
        {
            $service->likeUser($likedUserId);
        }
        else
        {
            $service->dislikeUser($likedUserId);
        }

        // Возвращаем состояние после изменения
        return [
            'liked' => $isLikeAction,
        ];
    }
}

Интеграция с компонентом

Компонент my:user.card был создан ранее. Чтобы показывать информацию о лайках, внесите изменения в файлы компонента.

Шаблон template.php

В файле /install/components/my/user.card/templates/.default/template.php добавьте блок лайка с текстом Нравится.

  • Атрибут data-user-id хранит идентификатор пользователя, карточку которого лайкают. Он нужен JavaScript.

  • Класс my-user-card__like-text--liked определяет внешний вид активного лайка.

<?php
use Bitrix\Main\Localization\Loc;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();

/**
 * @var array $arParams
 * @var array $arResult
 * @var CMain $APPLICATION
 * @var CBitrixComponent $component
 * @var CBitrixComponentTemplate $this
 */

$additionalUserCardContainers = $arResult['HAS_LIKE'] ? 'my-user-card__like-text--liked' : '';
?>

<div class="my-user-card">
    <?php if (isset($arResult['PERSONAL_PHOTO_SRC'])): ?>
    <div class="my-user-card__avatar">
        <img
            class="js-my-user-card-avatar-show-in-new-page"
            src="<?= $arResult['PERSONAL_PHOTO_SRC'] ?>"
            alt="<?= $arResult['NAME'] ?>"
        >
    </div>
    <?php endif; ?>

    <div class="my-user-card__info">
        <h2 class="my-user-card__name">
            <?= $arResult['NAME'] ?>
        </h2>

        <?php if ($arParams['SHOW_EMAIL'] === 'Y'): ?>
        <p class="my-user-card__email">
            <span><?= Loc::getMessage('USER_CARD_EMAIL_LABEL') ?></span>
            <span><?= $arResult['EMAIL'] ?></span>
        </p>
        <?php endif; ?>
    </div>
    <?php if ($USER->IsAuthorized()): ?>
    <div class="my-user-card__like-container">
        <span
            class="my-user-card__like-text js-my-user-card-like <?= $additionalUserCardContainers ?>"
            data-user-id="<?= $arParams['USER_ID'] ?>"
        >
            Нравится
        </span>
    </div>
    <?php endif; ?>
</div>

Скрипт script.js

В файл /install/components/my/user.card/templates/.default/script.js добавьте обработку клика на текст Нравится.

BX.ready(() => {
    // Обработка клика на аватар
    document.querySelectorAll('.js-my-user-card-avatar-show-in-new-page').forEach((item) => {
        BX.Event.bind(item, 'click', (e) => {
            window.open(item.src, '_blank');
        });
    });

    // Обработка клика на текст "Нравится"
    document.querySelectorAll('.js-my-user-card-like').forEach((element) => {
        BX.Event.bind(element, 'click', (e) => {
            const userId = element.dataset.userId;

            // Отправляем AJAX-запрос к контроллеру
            BX.ajax.runAction(
                'my:module.user.like',
                {
                    data: { likedUserId: userId }
                }
            ).then((response) => {
                // Успешный ответ: обновляем внешний вид
                if (response.status === 'success') {
                    if (response.data.liked)
                    {
                        element.classList.add('my-user-card__like-text--liked');
                    }
                    else
                    {
                        element.classList.remove('my-user-card__like-text--liked');
                    }
                }
            }).catch((response) => {
                console.error('Error:', response.errors);
            });
        });
    });
});

Стили style.css

В файл /install/components/my/user.card/templates/.default/style.css добавьте стили для текста лайка .

.my-user-card {
    background: #cff9f2;
    padding: 15px;
    width: 230px;
}

.my-user-card__avatar {
    margin: 0 0 15px 0;
}

.my-user-card__avatar img {
    cursor: pointer;
    max-width: 200px;
}

.my-user-card__info {
    text-align: left;
}

.my-user-card__name {
    font-size: 1.5rem;
    margin: 0 0 10px 0;
}

.my-user-card__email {
    font-size: 1rem;
}

.my-user-card__like-container {
    margin-top: 10px;
}

.my-user-card__like-text {
    cursor: pointer;
    transition: color 0.3s ease, font-weight 0.3s ease;
    user-select: none;
    color: #999;
    font-weight: normal;
}

.my-user-card__like-text:hover {
    text-decoration: underline;
}

/* Активный лайк — синий и жирный */
.my-user-card__like-text--liked {
    color: #0066cc;
    font-weight: bold;
}

Класс компонента class.php

Внесите изменения в основной класс компонента /install/components/my/user.card/class.php:

  • подключите ServiceLocator и LikeService,

  • в выборку пользователя добавьте поле ID,

  • в arResult добавьте проверку лайков HAS_LIKE,

  • добавьте метод isUserLiked(), который проверяет лайк через LikeService.

<?php
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Loader;
use My\Module\Services\LikeService;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();

class UserCardComponent extends CBitrixComponent
{
    /**
     * Подготавливаем входные параметры
     *
     * @param  array $arParams
     *
     * @return array
     */
    public function onPrepareComponentParams($arParams)
    {
        $arParams['USER_ID'] ??= 0;
        $arParams['SHOW_EMAIL'] ??= 'Y';

        return $arParams;
    }
    /**
     * Основной метод выполнения компонента
     *
     * @return void
     */

    public function executeComponent()
    {
        if (!Loader::includeModule('my.module'))
        {
            ShowError('Модуль my.module не установлен');

            return;
        }

        // кешируем результат, чтобы не делать постоянные запросы к базе
        if ($this->startResultCache())
        {
           $this->initResult();

            // в случае если ничего не найдено, отменяем кеширование
            if (empty($this->arResult))
            {
                $this->abortResultCache();
                ShowError('Пользователь не найден');

                return;
            }
            $this->includeComponentTemplate();
        }
    }

    /**
     * Инициализируем результат
     *
     * @return void
     */
    private function initResult(): void
    {
        $userId = (int)$this->arParams['USER_ID'];
        if ($userId < 1)
        {
            return;
        }

        $user = \Bitrix\Main\UserTable::query()
            ->setSelect([
                'ID',
                'NAME',
                'EMAIL',
                'PERSONAL_PHOTO',
            ])
            ->where('ID', $userId)
            ->fetch()
            ;

        if (empty($user))
        {
            return;
        }

        $this->arResult = [
            'NAME' => $user['NAME'],
            'EMAIL' => $user['EMAIL'],
            'HAS_LIKE' => $this->isUserLiked((int)$user['ID']),
        ];

        // получаем путь до аватарки, в случае если она указана
        if (!empty($user['PERSONAL_PHOTO']))
        {
            $this->arResult['PERSONAL_PHOTO_SRC'] = \CFile::GetPath($user['PERSONAL_PHOTO']);
        }
    }

     /**
     * Проверяем, поставил ли текущий пользователь лайк
     *
     * @return void
     */
    private function isUserLiked(int $userId): bool
    {
        /**
         * @var LikeService $service
         */

        // Используем ServiceLocator, а не new LikeService(),
        // чтобы компонент и контроллер работали с одним состоянием:
        // если контроллер изменил cookie — компонент сразу увидит изменения
        $service = ServiceLocator::getInstance()->get(LikeService::class);

        return $service->isLiked($userId);
    }
}

Настройка .settings.php

Файл /local/modules/my.module/.settings.php обязателен для работы контроллеров. Добавьте регистрацию контроллера. В результате становится возможен вызов my:module.user.like.

  • defaultNamespace должен соответствовать пространству имен контроллера.

  • Двойные обратные слеши \\ — экранирование в PHP-строках.

  • readonly: true запрещает изменение настроек через интерфейс.

<?php
return [
    'controllers' => [
        'value' => [
            'defaultNamespace' => '\\My\\Module\\Controller',
        ],
        'readonly' => true,
    ]
];

Архив модуля с контроллером

Готовый модуль с контроллером можно скачать в архиве. Для работы распакуйте архив в папку /local/modules/.

Как разместить компонент на сайте

  1. В административном разделе откройте страницу Marketplace > Установленные решения.

  2. Установите модуль Мой модуль (my.module).

  3. Создайте страницу и разместите на ней компонент Карточка пользователя.

После настройки компонента авторизованные пользователи увидят текст «Нравится» в карточке. Изначально текст будет серым, но, если поставить лайк, цвет изменится на синий.