# Стандартный маршрутизатор

## Введение

`Zend_Controller_Router_Rewrite` является стандартным маршрутизатором фреймворка. Маршрутизация - это процесс принятия конечной точки URI (той части URI, которая идет после базового URL) и ее разложения на параметры для определения того, какой контроллер и какое действие этого контроллера должны получить запрос. Значения контроллера, действия и необязательных параметров сохраняются в объекте `Zend_Controller_Request_Http`, который затем обрабатывается диспетчером `Zend_Controller_Dispatcher_Standard`. Маршрутизация производится только один раз – когда вначале получен запрос и до того, как первый контроллер будет запущен.

`Zend_Controller_Router_Rewrite` спроектирован для того, чтобы обеспечить функциональность, подобную mod_rewrite, с использованием чистого PHP. Он отчасти основан на маршрутизации, используемой в Ruby on Rails и не требует каких-либо предварительных знаний о перезаписи URL веб-сервером. Он спроектирован для работы с единственным правилом mod_rewrite, пример которого приведен ниже:

    RewriteEngine on
    RewriteRule !\.(js|ico|gif|jpg|png|css|html)$ index.php

или (более предпочтительный вариант):

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} -s [OR]
    RewriteCond %{REQUEST_FILENAME} -l [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^.*$ - [NC,L]
    RewriteRule ^.*$ index.php [NC,L]

Rewrite Router может также использоваться с веб-сервером IIS версии 7.0 и ниже, если Isapi_Rewrite был установлен как расширение Isapi со следующими правилами перезаписи:

    RewriteRule ^[\w/\%]*(?:\.(?!(?:js|ico|gif|jpg|png|css|html)$)[\w\%]*$)? /index.php [I]

### IIS Isapi_Rewrite

Если используется IIS, то `$_SERVER['REQUEST_URI']` не будет определен, либо будет установлен как пустая строка. В этом случае `Zend_Controller_Request_Http` попытается использовать `$_SERVER['HTTP_X_REWRITE_URL']`, значение которого устанавливается расширением Isapi_Rewrite.

IIS 7.0 имеет свой собственный модуль перезаписи URL, и он может быть сконфигурирован следующим образом:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
         <system.webServer>
             <rewrite>
                 <rules>
                     <rule name="Imported Rule 1" stopProcessing="true">
                         <match url="^.*$" />
                         <conditions logicalGrouping="MatchAny">
                             <add input="{REQUEST_FILENAME}"
                                 matchType="IsFile" pattern=""
                                 ignoreCase="false" />
                             <add input="{REQUEST_FILENAME}"
                                 matchType="IsDirectory"
                                 pattern="" ignoreCase="false" />
                         </conditions>
                         <action type="None" />
                     </rule>
                     <rule name="Imported Rule 2" stopProcessing="true">
                         <match url="^.*$" />
                         <action type="Rewrite" url="index.php" />
                     </rule>
                 </rules>
             </rewrite>
         </system.webServer>
    </configuration>

Если используется Lighttpd, то корректным будет следующее правило перезаписи:

    url.rewrite-once = (
        ".*\?(.*)$" => "/index.php?$1",
        ".*\.(js|ico|gif|jpg|png|css|html)$" => "$0",
        "" => "/index.php"
    )

## Использование маршрутизатора

Для того, чтобы правильно использовать маршрутизатор, вы должны инстанцировать его, добавить пользовательские маршруты, и внедрить его во фронт-контроллер. Следующий код иллюстрирует эту процедуру:

    // Создание маршрутизатора

    $router = $ctrl->getRouter(); // по умолчанию возвращает rewrite router
    $router->addRoute(
        'user',
        new Zend_Controller_Router_Route('user/:username',
                                         array('controller' => 'user',
                                               'action' => 'info'))
    );

## Базовые операции Rewrite Router

Сущностью RewriteRouter (перезаписывающий маршрутизатор) является определение пользовательских маршрутов. Маршруты добавляются посредством вызовом метода `addRoute()` и передачей ему экземпляра класса, реализующего `Zend_Controller_Router_Route`. Например:

    $router->addRoute('user',
                      new Zend_Controller_Router_Route('user/:username'));

Rewrite Router поставляется вместе с шестью базовыми типами маршрутов (один из которых является специальным):

- *

Маршруты могут использоваться несколько раз для создания цепочки или пользовательской схемы маршрутизации в приложении. Вы можете использовать любое количество маршрутов в любой конфигурации, за исключением маршрута Module, который предпочтительно должен использоваться один раз и, возможно, как наиболее общий маршрут (например, в качестве используемого по умолчанию). Каждый маршрут будет в подробностях описан ниже.

Первым параметром метода `addRoute()` является имя маршрута. Он используется в качестве идентификатора для получения маршрутов из маршрутизатора (например, в целях генерации URL). Вторым параметром является сам маршрут.

Наиболее часто имя маршрута используется через хелпер для URL компоненты `Zend_View`:

    <a href=
    "<?php echo $this->url(array('username' => 'martel'), 'user') ?>">Martel</a>

В результате значением атрибута href будет `user/martel`.

Маршрутизация - простой процесс итерации по всем предоставленным маршрутам и сопоставления их определений с текущим URI запроса. Когда найдено соответствие, то из объекта маршрута возвращаются значения переменных и добавляются в объект `Zend_Controller_Request` для дальнейшего использования в диспетчере и пользовательских контроллерах. Если соответствие не найдено, то проверяется следующий маршрут в цепочке.

Если нужно определить выбранный маршрут, то можно использовать метод `getCurrentRouteName()`, он возвращает идентификатор, который использовался при регистрации маршрута в маршрутизаторе. Если требуется получить объект, то используйте `getCurrentRoute()`.

### Обратный порядок сопоставления

Маршруты сопоставляются в обратном порядке, поэтому удостоверьтесь, что наиболее общие маршруты определены первыми.

### Возвращаемые значения

Значения, возвращаемые при маршрутизации, получаются из параметров URL или определенных пользователем значений по умолчанию. Эти переменные позднее могут быть позднее получены через методы `Zend_Controller_Request::getParam()` и `Zend_Controller_Action::_getParam()`

Есть три специальные переменные, которые могут использоваться в маршрутах: 'module', 'controller' и 'action'. Эти переменные используются диспетчером `Zend_Controller_Dispatcher` для нахождения контроллера и действия, которым передается управление.

### Специальные переменные

Имена этих переменных могут быть другими, если вы измените их через методы `setControllerKey` и `setActionKey`.

## Маршруты, используемые по умолчанию

`Zend_Controller_Router_Rewrite` изначально сконфигурирован с одним маршрутом по умолчанию, который будет соответствовать URI вида `контроллер/действие`. Кроме того, в качестве первого элемента пути может быть указано имя модуля, это позволяет использовать URI вида `модуль/контроллер/действие`. Этот маршрут будет также соответствовать любым дополнительным параметрам, по умолчанию добавляемым в конец URI - `контроллер/действие/переменная1/значение1/переменная2/значение2`.

Некоторые примеры того, чему будут соответствовать такие маршруты:

    // Допустим, есть следующие настройки:
    $ctrl->setControllerDirectory(
        array(
            'default' => '/path/to/default/controllers',
            'news'    => '/path/to/news/controllers',
            'blog'    => '/path/to/blog/controllers'
        )
    );

    Только модуль:
    http://example/news
        module == news

    Если модуль не найден, то считается, что это имя контроллера:
    http://example/foo
        controller == foo

    Модуль + контроллер:
    http://example/blog/archive
        module     == blog
        controller == archive

    Модуль + контроллер + действие:
    http://example/blog/archive/list
        module     == blog
        controller == archive
        action     == list

    Модуль + контроллер + действие + параметры:
    http://example/blog/archive/list/sort/alpha/date/desc
        module     == blog
        controller == archive
        action     == list
        sort       == alpha
        date       == desc

Маршрутом, используемым по умолчанию, является объект `Zend_Controller_Router_Route_Module`, сохраненный в RewriteRouter под именем (индексом) 'default'. Он создается приблизительно следующим образом:

    $compat = new Zend_Controller_Router_Route_Module(array(),
                                                      $dispatcher,
                                                      $request);
    $this->addRoute('default', $compat);

Если вы не хотите использовать этот маршрут по умолчанию в своей схеме маршрутизации, то можете переопределить его путем создания собственного маршрута по умолчанию (т.е. сохранения его под именем 'default') или полностью удалить его через метод `removeDefaultRoutes()`:

    // Удаление всех маршрутов по умолчанию
    $router->removeDefaultRoutes();

## Базовый URL и поддиректории

RewriteRouter может использоваться в поддиректориях (например, `http://domain.com/~user/application-root/`), в этом случае базовый URL приложения (`/~user/application-root`) должен автоматически определяться в объекте `Zend_Controller_Request_Http` и соответствующим образом использоваться.

Если базовый URL определяется некорректно, то вы можете переопределить его через метод `setBaseUrl()` объекта `Zend_Controller_Request_Http` (см. ):

    $request->setBaseUrl('/~user/application-root/');

## Глобальные параметры

Используя метод `setGlobalParam`, вы можете устанавливать глобальные параметры в маршрутизаторе, которые будут автоматически подставляться в маршрут при сборке. Если был установлен глобальный параметр, но при сборке тот же параметр был передан напрямую, то переданное значение параметра заменяет собой глобальное. Вы можете устанавливать глобальный параметр следующим образом:

    $router->setGlobalParam('lang', 'en');

## Типы маршрутов

## Использование Zend_Config вместе с RewriteRouter

Иногда может быть более удобным обновлять конфигурационный файл с новыми маршрутами, чем изменять код. Это возможно благодаря методу `addConfig()`. В сущности, вы создаете конфигурацию, совместимую с `Zend_Config`, считываете ее в своем коде и передаете RewriteRouter.

В качестве примера рассмотрим следующий INI-файл:

    [production]
    routes.archive.route = "archive/:year/*"
    routes.archive.defaults.controller = archive
    routes.archive.defaults.action = show
    routes.archive.defaults.year = 2000
    routes.archive.reqs.year = "\d+"

    routes.news.type = "Zend_Controller_Router_Route_Static"
    routes.news.route = "news"
    routes.news.defaults.controller = "news"
    routes.news.defaults.action = "list"

    routes.archive.type = "Zend_Controller_Router_Route_Regex"
    routes.archive.route = "archive/(\d+)"
    routes.archive.defaults.controller = "archive"
    routes.archive.defaults.action = "show"
    routes.archive.map.1 = "year"
    ; ИЛИ: routes.archive.map.year = 1

Этот INI-файл может быть затем прочитан в объект `Zend_Config` как показано ниже:

    $config = new Zend_Config_Ini('/path/to/config.ini', 'production');
    $router = new Zend_Controller_Router_Rewrite();
    $router->addConfig($config, 'routes');

В примере выше мы говорим маршрутизатору, чтобы он использовал раздел 'routes' в файле INI для своих маршрутов. Ключ первого уровня в этом разделе используется для определения имени маршрута, в примере выше определяются маршруты 'archive' и 'news'. Каждый маршрут требует, как минимум, запись 'route' и одну или более записей 'defaults'; опционально может быть одна или более записей 'reqs' (сокращение от 'required'). Все это соответствует трем аргументам, передаваемым объекту `Zend_Controller_Router_Route_Interface`. Опция с ключом 'type' может использоваться для определения класса, используемого для данного маршрута; по умолчанию используется класс `Zend_Controller_Router_Route`. В примере выше для маршрута 'news' должен использоваться класс `Zend_Controller_Router_Route_Static`.

## Создание подклассов маршрутизатора

Стандартный RewriteRouter создан с тем, чтобы предоставлять полный набор тех функциональных возможностей, которые могут вам понадобиться. Как правило, вам нужно будет только создать новый тип маршрута для того, чтобы получить новый или измененный функционал сверх уже существующих типов маршрутов.

В какой-то момент вы можете захотеть использовать другую парадигму маршрутизации. Интерфейс `Zend_Controller_Router_Interface` дает минимальную информацию, необходимую для создания маршрута и содержит всего один метод.

    interface Zend_Controller_Router_Interface
    {
      /**
       * @param  Zend_Controller_Request_Abstract $request
       * @throws Zend_Controller_Router_Exception
       * @return Zend_Controller_Request_Abstract
       */
      public function route(Zend_Controller_Request_Abstract $request);
    }

Маршрутизация производится только один раз - когда в систему поступил первый запрос. Назначение маршрутизатора состоит в определении контроллера, действия и опциональных параметров, основываясь на переменных запроса, и установке их в запросе. Затем объект запроса передается диспетчеру. Если не найден соответствующий маршрут, то маршрутизатор не должен ничего делать с объектом запроса.