События и свой код
Три рычага, по возрастанию силы: слушатель события, свой роут, свой контроллер. Брать надо самый слабый из тех, что справится, — чем слабее рычаг, тем больше кода продолжает обновляться вместе с компонентом.
Не правьте файлы в core/components/pbauth/
Они перезаписываются при обновлении, и правка исчезает молча. Всё, что ниже, — способы обойтись без этого.
Слушатель события
Класс с методом handle(array $params, string $event). Кладётся куда угодно в core/App/, называется в listeners.
<?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
use Boshnik\PbAuth\Events\Dispatcher;
use PageBlocks\App\Events\Auth\SendWelcomeLetter;
return [
'listeners' => [
Dispatcher::AFTER_REGISTER => [SendWelcomeLetter::class],
],
];Классов на одно событие можно повесить сколько угодно — выполнятся по очереди. Вместо имени класса принимается любой callable, в том числе замыкание.
Зарегистрировать слушателя на ходу, не через конфиг:
Dispatcher::listen(Dispatcher::AFTER_LOGIN, function (array $params, string $event) {
// …
});Упавший слушатель не ломает действие
Исключение внутри handle() ловится, пишется в лог MODX и не прерывает ни регистрацию, ни вход. Так и задумано: терять регистрацию из-за неотправленного письма нельзя. Из этого же следует, что слушатель не может запретить действие — для запрета нужен свой контроллер.
Два момента, и разница между ними важная
- До сохранения (
USER_SAVING) — пользователь ещё не записан в базу. Здесь меняют сам объект: дописывают поля, подставляют значения. Сохранять не нужно, компонент сохранит следом. - После (
AFTER_REGISTER,AFTER_LOGINи остальные) — всё уже записано. Здесь отправляют письма, пишут в журнал, дёргают внешний сервис.
Все события
| Когда | Константа | Что приходит в $params |
|---|---|---|
| перед сохранением — и при регистрации, и при правке профиля | USER_SAVING | user, profile, validated, action (register или profile) |
| пользователь зарегистрирован | AFTER_REGISTER | user, profile, validated |
| пользователь вошёл | AFTER_LOGIN | user |
| пользователь вышел | AFTER_LOGOUT | user |
| профиль сохранён | AFTER_PROFILE_UPDATE | user, profile, validated |
| почта подтверждена по ссылке из письма | AFTER_VERIFY_EMAIL | user |
| пароль восстановлен по ссылке | AFTER_RESET_PASSWORD | user |
| пароль изменён в профиле | AFTER_CHANGE_PASSWORD | user |
| ссылка подтверждения отправлена повторно | AFTER_RESEND_VERIFICATION | user, email |
| пользователь включил второй фактор | TWO_FACTOR_ENABLED | user |
| пользователь выключил второй фактор | TWO_FACTOR_DISABLED | user |
| соцсеть привязана к аккаунту | SOCIAL_LINKED | user, provider |
| соцсеть отвязана | SOCIAL_UNLINKED | user, provider |
| менеджер вошёл под чужой учётной записью | AFTER_IMPERSONATE | user, 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
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
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);
}
}'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{
"success": false,
"message": "Проверьте поля",
"errors": {
"username": "Пользователь не найден"
},
"redirect": ""
}Успех — то же, с success: true и адресом в redirect; на ошибках валидации код ответа 422. Этого хватает, чтобы форму рисовал свой фронтенд: ровно так работает штатный скрипт PageBlocks при включённом pageblocks_load_scripts.
Это ответ формы, а не контракт API
Набор полей ответа — общий для всех форм PageBlocks, и на него можно опираться. Но адреса /login и /register остаются формами: они ждут CSRF-токен и живут в сессии. Для безсессионного клиента (мобильное приложение) этого недостаточно — токенов pbAuth не выдаёт.
Оба обычных сценария при этом работают без JS: без скрипта форма отправляется обычным POST'ом и отвечает редиректом с ошибками в сессии.