Drupal → Программная реализация ЧПУ

10.01.2020

Задача — из адреса в формате /catalog/notebooks?price_from=xxx&price_to=yyy, в котором /catalog/notebooks это синоним термина, а ?price_from=xxx&price_to=yyy динамические параметры, сделать человекопонятный адрес в формате /catalog/notebooks/price-xxx-yyy.

Теория есть у niklan'a, поэтому сразу к коду.

Для решения надо создать сервис с двумя методами:
processOutbound() — изменяет исходящие адреса в новый формат
processInbound() — изменяет входящие адреса из нового формата в старый

src/ModulenamePathProcessor.php:

class ModulenamePathProcessor implements InboundPathProcessorInterface, OutboundPathProcessorInterface {

  /**
   * {@inheritdoc}
   */
  public function processInbound($path, Request $request) {
    // Convert path from "/catalog/foo/bar/price-123-456" to "/catalog/foo/bar?price_from=123&price_to=456"
    if (strpos($path, '/catalog/') === 0 && preg_match('/\/price-([^\/]+)$/', $path, $matches)) {
      // Delete price from path
      $path = str_replace($matches[0], '', $path);
      
      // Move price to $_GET
      $price = explode('-', $matches[1]);
      $request->query->set('price_from', $price[0]);
      $request->query->set('price_to', $price[1]);
    }

    return $path;
  }

  /**
   * {@inheritdoc}
   */
  public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
    // Convert path from "/catalog/foo/bar?price_from=123&price_to=456" to "/catalog/foo/bar/price-123-456"
    if (strpos($path, '/catalog/') === 0 && isset($options['query']['price_from']) && isset($options['query']['price_to'])) {
      // Move price query params to path
      $path .= '/price-' . $options['query']['price_from'] . '-' . $options['query']['price_to'];

      // Delete price query params
      unset($options['query']['price_from']);
      unset($options['query']['price_to']);
    }

    return $path;
  }

}

modulename.services.yml:

services:
  modulename.path_processor:
    class: Drupal\modulename\ModulenamePathProcessor
    tags:
      - { name: path_processor_inbound, priority: 200 } # Run before \Drupal\Core\PathProcessor\PathProcessorAlias::processInbound()
      - { name: path_processor_outbound, priority: 200 } # Run after \Drupal\Core\PathProcessor\PathProcessorAlias::processOutbound()

По аналогии можно сделать ЧПУ любой сложности.

Замечания:
— Метод processOutbound() надо использовать с осторожностью, потому что он вызывается для всех url-адресов на странице, а это несколько десятков, а то и сотен раз на каждый запрос.
— Результат processInbound() кэшируется (см. RouteProvider::getRouteCollectionForRequest()), поэтому после изменения метода надо очищать кэш.
— В processInbound() можно менять только $path и $request->query, другие данные, например $request->attributes, менять бесполезно (опять же из-за кэша).

Важное замечание: этот способ пока не работает с модулем Redirect и включённой опцией "Enforce clean and canonical URLs". При открытии страницы /catalog/notebooks/price-xxx-yyy будет происходит редирект на /catalog/notebooks. Issue с временным решением.

Написанное актуально для
Drupal 8
Похожие записи

Комментарии

if (\strpos($path, '/catalog/') === 0 && \preg_match('/(.*)\/price\-(\d+)\-(\d+)$/', $path, $matches)) {
    list(, $path, $price_from, $price_to) = $matches;
    $request->query->set('price_from', $price_from);
    $request->query->set('price_to', $price_to);
}
unset($options['query']['price_from'], $options['query']['price_to']);
isset($options['query']['price_from'], $options['query']['price_to'])

Добавить комментарий