---
title: 'Немного динамического раутинга в Drupal 7'
url: 'https://graker.ru/news/2015/02/26/nemnogo_dinamicheskogo_rautinga_v_drupal_7'
language: ru
type: post
author: 'Роман Грачев'
published: '2015-02-26T00:17:10+03:00'
updated: '2018-03-23T10:54:11+03:00'
categories:
  - Программирование
  - Друпальчики
---

# Немного динамического раутинга в Drupal 7

В Друпале, как известно, адреса основных сущностей, таких как пользователи и материалы, имеют вид user/123 и node/123 соответственно (где 123 — это id сущности). Если мы хотим, например, сделать страницу со всеми фотографиями для каждого пользователя, то скорее всего мы сделаем ее с адресом вроде user/123/photos. Ну потому что надо же откуда-то id пользователя взять и загрузить все его фотографии. И этот адрес будет хорош всем кроме одного — пользователю непонятно, почему он, Василий Петрович Пупкин, называется «123». Пользователю конечно было бы удобнее видеть свои фотографии по адресу vasya/photos. То есть неплохо было бы решить проблему [ЧПУ](https://ru.wikipedia.org/wiki/%D0%A7%D0%9F%D0%A3_%28%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82%29).

Обычно (для отдельных страниц) в Друпале эта проблема решается с помощью синонимов (path aliases), то есть каждой странице (user/123) ставится в соответствие уникальный адрес (vasya), и страница /user/123 становится доступной по адресу /vasya. А чтобы избежать дубликатов страниц (т.е. одинаковых страниц с разными адресами), делается автоматический редирект с оригинальной страницы на ее синоним: при заходе на страницу user/123 пользователя перенаправляет на страницу /vasya.

Однако в случае когда страница динамическая (все фотографии данного пользователя) — получается, что нужно для каждой страницы вида `user/{uid}/photos` завести свой синоним. И обновлять его, если обновится синоним для страницы пользователя. И удалять, если пользователь удалится. И добавлять, если пользователь добавится. В общем, мало того, что для каждого пользователя нам придется не один, а два синонима хранить. Так нам еще и CRUD для синонимов поддерживать понадобится. А что если мы хотим еще страницу с блогами пользователя, страницу с его друзьями? Или вообще, мы хотим страницу архива материалов адресом вида `user/123/archive/2014/09`?

К счастью, создавать синоним для каждого адреса динамической страницы не придется. Вместо этого можно генерировать «красивые» адреса программно с помощью хуков [`hook_url_inbound_alter()`](https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_url_inbound_alter/7) и [`hook_url_outbound_alter()`](https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_url_outbound_alter/7). Что эти хуки делают? Позволяют переписать входящие и исходящие url-ы.

Первый хук `hook_url_inbound_alter(&$path, $original_path, $path_language)` отвечает за обработку входящего запроса. При этом в `$original_path` содержится запрос, как он пришел в систему от пользователя, а в `$path` конструируется тот путь, который будет обработан Друпалом. Иными словами, если пользователь наберет адрес `/vasya/photos`, то чтобы Друпал узнал, что это вообще за страница, нам нужно в `$path` положить настоящий адрес `user/123/photos`. Вот так:

```
function mymodule_url_inbound_alter(&$path, $original_path, $path_language) {
  $parts = explode('/', $original_path);
  if ($parts[1] == 'photos') {
    //проверим, является ли первая часть адреса синонимом для пользователя
    $path_info = path_load(array('alias' => $parts[0]));
    if ($path_info) {
      $source_parts = explode('/', $path_info['source']);
      if (($source_parts[0] == 'user') && ctype_digit($source_parts[1])) {
        $path = 'user/' . $source_parts[1] . '/' . 'photos';
      }
    }
  }
}
```

Теперь при заходе на страницу `vasya/photos`, Друпал будет знать, что нужно показать страницу фото (user/123/photos). Но сама страница `user/123/photos` будет по-прежнему существовать, что может быть нежелательно с точки зрения SEO. Добавим редирект на этот случай:

```
function mymodule_url_inbound_alter(&$path, $original_path, $path_language) {
  $parts = explode('/', $original_path);
  if ($parts[1] == 'photos') {
    //проверим, является ли первая часть адреса синонимом для пользователя
    $path_info = path_load(array('alias' => $parts[0]));
    if ($path_info) {
      $source_parts = explode('/', $path_info['source']);
      if (($source_parts[0] == 'user') && ctype_digit($source_parts[1])) {
        $path = 'user/' . $source_parts[1] . '/' . 'photos';
      }
    }
  } else if (
          ($parts[0] == 'user') &&
          isset($parts[1]) &&
          ctype_digit($parts[1]) &&
          isset($parts[2]) &&
          ($parts[2] == 'photos')) {
    //получим синоним для пользователя и перейдем по ссылке синоним/photos
    $user_alias = path_load($parts[0] . '/' . $parts[1]);
    $goto_path = $user_alias['alias'] . '/' . 'photos';
    drupal_goto($goto_path);
  }
}
```

Друпал уже знает, как обрабатывать страницы вида `vasya/photos`. Но сам пользователь о существовании таких страниц еще не знает: на сайте-то ссылки на страницы с фотографиями по-прежнему показываются как `user/123/photos`. Вот чтобы это поправить, и нужен второй хук — `hook_url_outbound_alter(&$path, &$options, $original_path)`. В этом хуке можно изменить все исходящие ссылки при генерации текущей страницы. Для этого достаточно, чтобы ссылка генерировалась с вызовом функции [`url()`](https://api.drupal.org/api/drupal/includes!common.inc/function/url/7), а это верно почти для всех ссылок на сайте. В `$original_path` на входе у нас будет лежать `user/123/photos`, в `$path` мы должны положить новый url — `vasya/photos`.

```
function mymodule_url_outbound_alter(&$path, &$options, $original_path) {
  if (preg_match('|^user/([0-9]*)(/.*)?|', $path, $matches)) {
    $uid = $matches[1];
    if (isset($matches[2]) && ($matches[2] == '/photos')) {
      $user_alias = path_load('user/' . $uid);
      $path = $user_alias['alias'] . $matches[2];
    }
  }
}
```

Теперь в `href` нужных ссылок вместо `user/123/photos` будет `vasya/photos` — то что мы хотели.
