Нещодавно ми працював із сайтом на Drupal 9 і виявили дивну поведінку під час збереження певних типів вмісту. Здавалося б, сторінка збережена, але більшої частини вмісту зникало. Йому навіть не вдалося створити шляхи для сторінки на основі автоматичних правил шляху.
Копнувши глибше, ми виявили, що основною причиною проблеми було неправильно створене перенаправлення у хуку вставки в спеціальному коді, написаному на сайті. Цей код шукав створення певного типу вмісту, а потім примусово здійснив перенаправлення.
Це був більш-менш код, про який ідеться.
use Drupal\Core\Entity\EntityInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
function mymodule_entity_insert(EntityInterface $entity) {
if ($entity->getType() == 'article') {
(new RedirectResponse('/node/' . ($entity->id())))->send();
exit();
}
}
Сам по собі це не чудовий код, але найгірше – це виклик функції exit(). Це негайно зупиняє будь-яке виконання коду, через що тип вмісту було створено наполовину. Зупиняючи виконання коду таким чином, ми запобігаємо будь-яким іншим хукам або службам від дії на тип вмісту, який вставляється.
Вторинним ефектом цього є те, що він також обходить функції завершення роботи Drupal. Коли Drupal запускає запит сторінки, він реєструє кілька методів, які мають бути викликані, коли відповідь сторінки закривається. Це включає методи керування сеансом, але також може включати обробник cron (якщо cron налаштовано таким чином).
Ймовірно, є причина, чому була додана функція exit(). за замовчуванням Drupal виконає перенаправлення після створення сторінки, і я думаю, що початковий розробник, ймовірно, намагався запобігти переспрямуванню вгору. На жаль, вони створили більше проблем, ніж просто проблему перенаправлення.
Побачивши цей код, я задумався про те, як виконати перенаправлення, не створюючи проблем у Drupal. Відповідь не така однозначна, оскільки залежить від контексту, з яким ви маєте справу в даний момент. Я подумав, що було б корисно показати, як перенаправити сторінку Drupal залежно від місця написання коду.
Перенаправлення в контролерах
Перенаправлення в контролері, мабуть, найпростіша річ, оскільки вам просто потрібно повернути новий об’єкт Symfony\Component\HttpFoundation\RedirectResponse. Об’єкт перенаправлення приймає рядок, що вказує на сторінку, і його можна легко створити за допомогою об’єкта Drupal\Core\Url.
$url = Url::fromRoute('entity.node.canonical', ['node' => 1]);
return new RedirectResponse($url->toString());
Drupal побачить, що цей тип об’єкта повертається з дії контролера, і відповідно перенаправить сторінку. Це перенаправлення відбувається після завершення обробки вихідного коду.
Перенаправлення у формах
Якщо ви хочете перенаправити надсилання форми, коли вам потрібно використовувати метод setRedirect() для об’єкта стану форми під час обробника надсилання форми. Цей метод використовує маршрут, на який ви хочете переспрямувати, і його можна використовувати таким чином.
$form_state->setRedirect('entity.node.canonical', ['node' => 1]);
Крім того, ви можете використовувати метод setRedirectUrl() стану форми, який приймає об’єкт Drupal\Core\Url як аргумент.
$url = Url::fromRoute('entity.node.canonical', ['node' => 1]);
$form_state->setRedirectUrl($url);
Єдине застереження щодо цього методу полягає в тому, що ви не повинні також використовувати метод setRebuild() у стані форми, оскільки це призведе до того, що переспрямування не буде оброблено.
У наведених вище прикладах передбачається, що ви пишете клас форми, але що, якщо ви хочете виконати переспрямування на формі, над якою ви не маєте контролю? Використовуючи хук hook_form_alter(), ми можемо додати обробник подання форми в стан форми, а потім додати код перенаправлення до цього обробника.
Ось хук hook_form_alter().
function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form_id === 'node_article_form') {
$form['actions']['submit']['#submit'][] = 'mymodule_article_form_submit';
}
}
Обробник подання виглядав би приблизно так.
function mymodule_article_form_submit($form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.node.canonical', ['node' => 1]);
}
На мій погляд, саме так мав бути написаний оригінальний код-ін'єкція. Додавши хук зміни форми до форми та ввівши спеціальний обробник подання, переспрямування можна легко додати до стану форми, який потім може обробити обробник подання форми. Тоді перенаправлення буде виконано, і йому не доведеться боротися з іншими перенаправленнями, створеними Drupal.
Перенаправлення в події
Якщо ви берете участь у події, підхід знову дещо інший. Тут вам потрібно вставити RedirectResponse в об’єкт Event за допомогою методу setResponse(). Наступний код буде перенаправлено, якщо умова виконана.
class MyEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
$events['kernel.request'] = ['someEvent', 28];
return $events;
}
private function someEvent(KernelEvent $event) {
if (/* some condition */) {
$url = Url::fromRoute('entity.node.canonical', ['node' => 1]);
$response = new RedirectResponse($url->toString());
$event->setResponse($response);
}
}
}
Створення об’єкта RedirectResponse відбувається точно так само, як зазвичай. У цьому випадку Drupal виконає перенаправлення після обробки події. Переконайтеся, що ви включили своє перенаправлення в умову (особливо під час відповіді на подію kernel.request), інакше це призведе до нескінченного циклу перенаправлення.
Майте на увазі, що тут також можуть бути викликані інші події, які можуть замінити або змінити відповідь. У цьому випадку вам потрібно змінити вагу підписника на подію, щоб переконатися, що вона відбувається останньою під час обробки події.
Перенаправлення в хуках
Також можна (хоча і не рекомендується) виконувати перенаправлення в хуку. Найкраще генерувати об’єкт Url із маршруту (а не жорстко кодувати шлях вузла в перенаправлення), оскільки це дозволить змінити шлях у майбутньому.
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
function mymodule_node_insert(EntityInterface $entity) {
if ($entity->getType() == 'article') {
$url = Url::fromRoute('entity.node.canonical', ['node' => 1]);
$redirect = new RedirectResponse($url->toString());
$redirect->send();
}
}
Як правило, перенаправляти в межах хука – погана ідея. Вам слід або переглянути переспрямування в обробнику подання форми, або підписатися на подію та переспрямувати звідти.
Цікаво відзначити, що модуль Redirect, розроблений навколо перенаправлення, використовує обробник подій для створення та повернення перенаправлення в об’єкт події. Хоча модуль містить декілька хуків, жоден із них не стосується перенаправлення вмісту.
Як останнє зауваження. Що б ви не робили, не додавайте до свого коду Drupal такі функції, як die() або exit(), інакше ви зламаєте Drupal цікавим чином. Drupal має правильно завершити виконання коду для сторінки, а додавання подібних функцій призведе до поломки Drupal цікавими способами.
Коментарі