Skip to content

События и свой код ​

Три рычага, по возрастанию силы: слушатель события, свой роут, свой контроллер. Брать надо самый слабый из тех, что справится, — чем слабее рычаг, тем больше кода продолжает обновляться вместе с компонентом.

Не правьте файлы в core/components/pbauth/

Они перезаписываются при обновлении, и правка исчезает молча. Всё, что ниже, — способы обойтись без этого.

Слушатель события ​

Класс с методом handle(array $params, string $event). Кладётся куда угодно в core/App/, называется в listeners.

php
<?php

namespace PageBlocks\App\Events\Auth;

use Boshnik\PageBlocks\Support\Mail;

class SendWelcomeLetter
{
    public function handle(array $params, string $event): void
    {
        $user = $params['user'] ?? null;
        $validated = $params['validated'] ?? [];

        if (!$user) {
            return;
        }

        Mail::to($validated['email'])
            ->subject('Добро пожаловать!')
            ->view('file:auth/chunks/email.welcome', ['username' => $user->username])
            ->send();
    }
}
php
<?php

use Boshnik\PbAuth\Events\Dispatcher;
use PageBlocks\App\Events\Auth\SendWelcomeLetter;

return [
    'listeners' => [
        Dispatcher::AFTER_REGISTER => [SendWelcomeLetter::class],
    ],
];

Классов на одно событие можно повесить сколько угодно — выполнятся по очереди. Вместо имени класса принимается любой callable, в том числе замыкание.

Зарегистрировать слушателя на ходу, не через конфиг:

php
Dispatcher::listen(Dispatcher::AFTER_LOGIN, function (array $params, string $event) {
    // …
});

Упавший слушатель не ломает действие

Исключение внутри handle() ловится, пишется в лог MODX и не прерывает ни регистрацию, ни вход. Так и задумано: терять регистрацию из-за неотправленного письма нельзя. Из этого же следует, что слушатель не может запретить действие — для запрета нужен свой контроллер.

Два момента, и разница между ними важная ​

  • До сохранения (USER_SAVING) — пользователь ещё не записан в базу. Здесь меняют сам объект: дописывают поля, подставляют значения. Сохранять не нужно, компонент сохранит следом.
  • После (AFTER_REGISTER, AFTER_LOGIN и остальные) — всё уже записано. Здесь отправляют письма, пишут в журнал, дёргают внешний сервис.

Все события ​

КогдаКонстантаЧто приходит в $params
перед сохранением — и при регистрации, и при правке профиляUSER_SAVINGuser, profile, validated, action (register или profile)
пользователь зарегистрированAFTER_REGISTERuser, profile, validated
пользователь вошёлAFTER_LOGINuser
пользователь вышелAFTER_LOGOUTuser
профиль сохранёнAFTER_PROFILE_UPDATEuser, profile, validated
почта подтверждена по ссылке из письмаAFTER_VERIFY_EMAILuser
пароль восстановлен по ссылкеAFTER_RESET_PASSWORDuser
пароль изменён в профилеAFTER_CHANGE_PASSWORDuser
ссылка подтверждения отправлена повторноAFTER_RESEND_VERIFICATIONuser, email
пользователь включил второй факторTWO_FACTOR_ENABLEDuser
пользователь выключил второй факторTWO_FACTOR_DISABLEDuser
соцсеть привязана к аккаунтуSOCIAL_LINKEDuser, provider
соцсеть отвязанаSOCIAL_UNLINKEDuser, provider
менеджер вошёл под чужой учётной записьюAFTER_IMPERSONATEuser, manager

Константы — в Boshnik\PbAuth\Events\Dispatcher. validated — массив того, что пришло из формы и прошло проверку.

Плагины MODX тоже работают ​

Одновременно вызывается одноимённое системное событие MODX (pbAuthAfterRegister, pbAuthUserSaving и так далее) — на случай, если вам привычнее плагины.

Класс в конфигеПлагин на системном событии
после деплояработает сразунадо заново прикрепить в Система → События
что получаетобъекты: user, profile, validatedтолько скаляры: user_id, profile_id, email
может менять объектда, на USER_SAVINGнет

Объекты плагину не уходят намеренно: modUser в $scriptProperties плагину бесполезен и ломает кэширование событий. Поэтому USER_SAVING через плагин бессмысленен — менять там нечего.

Свои роуты ​

Адреса компонента лежат в core/components/pbauth/routes/auth.php, и править их не надо. Свои страницы добавляйте своим файлом в core/App/routes/:

php
<?php

use Boshnik\PageBlocks\Facades\Route;

Route::middleware('auth')->group(function () {
    Route::get('/profile/settings', 'ProfileSettingsController@show')->name('profileSettings');
});

middleware('auth') — только для вошедших, middleware('guest') — только для гостей. Контроллер кладётся в core/App/Http/Controllers/ — дальше это обычная разработка на PageBlocks, pbAuth тут ни при чём.

Порядок объявления значения не имеет: точный адрес всегда выигрывает у шаблонного. /profile/settings откроет вашу страницу, даже если где-то объявлен /profile/{alias}.

Только не называйте файл auth.php

Пока в core/App/routes/ лежит файл с именно этим именем, pbAuth считает, что сайт живёт на старой схеме, и свои адреса не подключает вообще — /login перестанет открываться. Любое другое имя годится.

Подмена контроллера ​

Последний рычаг — когда настроек и событий не хватило. Например, надо запретить регистрацию с некоторых адресов: слушатель этого не может, он не умеет сказать «нет».

core/App/Http/Controllers/Auth/RegisterController.php:

php
<?php

namespace PageBlocks\App\Http\Controllers\Auth;

use Boshnik\PageBlocks\Http\Request;

class RegisterController extends \Boshnik\PbAuth\Http\Controllers\Auth\RegisterController
{
    public function register(Request $request)
    {
        if ($this->isBlacklisted($request->ip())) {
            return response()->error('Регистрация с этого адреса закрыта');
        }

        return parent::register($request);   // дальше как обычно
    }

    protected function isBlacklisted(string $ip): bool
    {
        return in_array($ip, ['203.0.113.7'], true);
    }
}
php
'controllers' => [
    'register' => \PageBlocks\App\Http\Controllers\Auth\RegisterController::class,
],

Роуты компонента после этого ведут в ваш класс. Всё, что вы не переопределили, продолжает работать из компонента и продолжает обновляться — поэтому переопределяйте только то, что действительно нужно.

Ключи: auth (подтверждение почты), login, register, profile, forgot_password, reset_password, change_password, confirm_password, resend_verification, two_factor, social, impersonate.

Логика живёт в контроллерах, а не в сервисе

Отдельного объекта $auth с публичным API у компонента сейчас нет: сценарии входа и регистрации реализованы прямо в контроллерах. Поэтому «позвать регистрацию из своего кода» означает позвать метод контроллера — а не $auth->register($data).

Это признанный долг компонента, а не особенность архитектуры. Если вам нужна регистрация из крона, из консольной команды или из своего API-эндпоинта, наследуйте контроллер и зовите его метод; дублировать логику у себя не надо — она разойдётся с компонентом на первом же обновлении.

JSON вместо редиректа ​

Отдельного API у компонента нет, но форма отвечает по-разному в зависимости от запроса: PageBlocks смотрит на expectsJson(), то есть на Accept и X-Requested-With.

POST /login
Accept: application/json
X-Requested-With: XMLHttpRequest
json
{
    "success": false,
    "message": "Проверьте поля",
    "errors": {
        "username": "Пользователь не найден"
    },
    "redirect": ""
}

Успех — то же, с success: true и адресом в redirect; на ошибках валидации код ответа 422. Этого хватает, чтобы форму рисовал свой фронтенд: ровно так работает штатный скрипт PageBlocks при включённом pageblocks_load_scripts.

Это ответ формы, а не контракт API

Набор полей ответа — общий для всех форм PageBlocks, и на него можно опираться. Но адреса /login и /register остаются формами: они ждут CSRF-токен и живут в сессии. Для безсессионного клиента (мобильное приложение) этого недостаточно — токенов pbAuth не выдаёт.

Оба обычных сценария при этом работают без JS: без скрипта форма отправляется обычным POST'ом и отвечает редиректом с ошибками в сессии.

pbAuth — вход, регистрация и профиль для PageBlocks