Я работал с сайтом на Drupal и обнаружил странное поведение при сохранении определенных типов контента. Страница сохраняется, но большая часть контента отсутствует. Системе даже не удалось сгенерировать пути для страницы на основе автоматических правил пути.
Проанализировав поведение, я обнаружил, что основной причиной проблемы было неправильно созданное перенаправление в хуке вставки в пользовательском коде, написанном на сайте. Этот код искал создание определенного типа контента, а затем вызывал перенаправление.
Вот этот код.
use Drupal\Core\Entity\EntityInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
function module_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 module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if ($form_id === 'node_article_form') {
$form['actions']['submit']['submit'][] = 'module_article_form_submit';
}
}
Обработчик отправки будет выглядеть примерно так.
function module_article_form_submit($form, FormStateInterface $form_state) {
$form_state->setRedirect('entity.node.canonical', ['node' => 1]);
}
На мой взгляд, именно так должен был быть написан исходный неправильный код. Добавив к форме хук изменения формы и внедрив пользовательский обработчик отправки, перенаправление можно легко добавить в состояние формы, которое затем может быть обработано обработчиком отправки формы. После этого произойдет перенаправление, и ему не придется бороться ни с какими другими перенаправлениями, созданными Drupal.
Перенаправление в событиях
Если вы участвуете в событии, подход снова немного отличается. Здесь вам нужно внедрить RedirectResponse в объект Event, используя метод setResponse(). Следующий код будет перенаправлять, если условие было выполнено.
class EventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
$events['kernel.request'] = ['someEvent', 28];
return $events;
}
private function someEvent(KernelEvent $event) {
if (/* какое-то условие */) {
$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, разработанный для перенаправления, использует обработчик событий для создания и возврата перенаправления в объекте события. Хотя модуль содержит несколько хуков, ни один из них не связан с перенаправлением контента.
В качестве заключительного замечания. Что бы вы ни делали, не добавляйте такие функции, как die() или exit() в свой код Drupal, иначе вы сломаете Drupal таким образом. Drupal необходимо корректно завершить выполнение кода для страницы, и добавление подобных функций приведет к поломкам Drupal.