Содержание


Routing


Routing, роутинг, маршрутизация, МВЦ,MVC, ЗФ2, Zend Framework 2, ZF2, ру, ru





 

Routing (Маршрутизация) -  это процесс передачи запроса к соответствующему ему контроллеру для дальнейшей его обработки.

 

Как правило, маршрутизация заключается в анализе запроса URI и попытке найти соответствие, в заранее заданных путях, обращая внимание на ограничения. Если же ограничения позволяют перейти по заданному адресу, то возвращается список соответствий, в одном из которых должно находиться имя контроллера для выполнения. Маршрутизация так же может использовать различные параметры окружения URI запроса, таки как: host (хост) или  scheme (схема), query parameters (параметры запроса), headers (заголовки), request method (метод запроса)  и др.

 

Важно: Если Вы являетесь разработчиком и у Вас имеются знания про систему маршрутизации в Zend Framework 1.x, то необходимо учесть, что часть старой терминологии больше не применяется к Zend Framework 2.x. В новой системе нет маршрутизатора как такового в принципе. Каждый маршрут (route) может  соответствовать и собирать URI сам, что и делает их маршрутизаторами.

 

Базовый блок маршрутизации это Route:

namespace Zend\Mvc\Router;
 
use zend\Stdlib\RequestInterface as Request;
 
interface RouteInterface
{
    public static function factory(array $options = array());
    public function match(Request $request);
    public function assemble(array $params = array(), array $options = array());
}

Каждый Route принимает Request (запрос) и определяет, есть ли соответствие (match). Если да, то возвращается объект RouteMatch.

namespace Zend\Mvc\Router;
 
class RouteMatch
{
    public function __construct(array $params);
    public function setMatchedRouteName($name);
    public function getMatchedRouteName();
    public function setParam($name, $value);
    public function getParams();
    public function getParam($name, $default = null);
}

Как правило, при соответствии маршрута (Route), он определяет один или несколько параметров. Они передаются в RouteMatch, и объект может перебирать RouteMatch по его значениям.

$id = $routeMatch->getParam('id', false);
if (!$id) {
    throw new Exception('Required identifier is missing!');
}
$entity = $resource->get($id);

Обычно используются несколько маршрутов. Для того, чтобы облегчить этот процесс,Вы можете использовать совокупности (объединения) маршрутов, реализуя RouteStack:

namespace Zend\Mvc\Router;
 
interface RouteStackInterface extends RouteInterface
{
    public function addRoute($name, $route, $priority = null);
    public function addRoutes(array $routes);
    public function removeRoute($name);
    public function setRoutes(array $routes);
}

Как правило, маршруты просматриваются в порядке LIFO и находятся в RouteStack. Zend Framework предоставляет две  реализации этого интерфейса: SimpleRouteStack и TreeRouteStack. В каждом из них, Вы можете добавлять маршруты либо по одному, используя метод addRoute(), либо несколько, используя метод addRoutes().

// One at a time:
$route = Literal::factory(array(
    'route' => '/foo',
    'defaults' => array(
        'controller' => 'foo-index',
        'action'     => 'index',
    ),
));
$router->addRoute('foo', $route);
 
// In bulk:
$router->addRoutes(array(
    // using already instantiated routes:
    'foo' => $route,
 
    // providing configuration to allow lazy-loading routes:
    'bar' => array(
        'type' => 'literal',
        'options' => array(
            'route' => '/bar',
            'defaults' => array(
                'controller' => 'bar-index',
                'action'     => 'index',
            ),
        ),
    ),
));


Типы Маршрутизаторов  (Router Types)

 

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

 


SimpleRouteStack

 

Он принимает индивидуальные маршруты, которые полностью соответствуют логике. Затем перебирает стек всех маршрутов в порядке LIFO, пока не найдет полное совпадение. Таким образом, наиболее часто используемые маршруты даже быть зарегистрированы последними, а наименее часто -  первыми. Так же, необходимо убедиться, что если есть перекрывающиеся пути, то более приоритетный путь должен регистрироваться позже. Однако, есть так же возможность указывать приоритет, передавая его как третий параметр в метод addRoute().

 


TreeRouteStack

 

Zend\Mvc\Router\Http\TreeRouteStack предоставляет возможность регистрировать деревья маршрутов, а так же использовать алгоритм «В»-дерево для перебора маршрутов при поиске совпадений. У Вас появляется возможность создания одного маршрута с неограниченным количеством «детей».

 

TreeRouteStack состоит из следующих настроек:

 

- Базовый маршрут, «route», описывающий базовое совпадение. Является корнем дерева.

- Опция «route_plugins», настраиваемая в Zend\Mvc\Router\RoutePluginManager. Позволяет применять ленивую загрузку маршрутов (routes).

-  Опция «may_terminate», которая уведомляет router, что после нее нет никаких других сегментов.

- Опция - массив «child_routes»,  содержащая дополнительные маршруты (routes), которые являются наследниками базового маршрута (route).Тоесть построены от него. Каждый дочерний маршрут может быть сам TreeRouteStack (деревом) при необходимости. Кстати, HTTPмаршрут с названием «Part» работает по такому принципу.

 

Во время поиска, при соответствии маршрута в TreeRouteStack возвращаются все параметры из каждого сегмента дерева.

 

TreeRouteStack может быть единственным маршрутом во всем приложении, или описывать всего навсего один из сегментов путей Вашего приложения.

Примеры по TreeRouteStack смотрите в разделе про «Part route»

 



Типы HTTP маршрутов (Route)

 

В ZendFramework 2.0 существуют следующие типы маршрутов:

 



Zend\Mvc\Router\Http\Hostname

 

Маршрут «Hostname» пытается сопоставить hostname (имя хоста), зарегистрированное в запросе с конкретными критериями. Вы можете встретить это, на пример в таких формах:

«subdomain.domain.tld»

«:subdomain.domain.tld»

 

В примере, представленном выше, второй маршрут вернет ключ «subdomain», как часть совпадающего маршрута.

 

Для любого сегмента имени хоста (hostname) Вы можете добавить необходимые ограничения. Как пример, если сегмент «subdomain» должен совпадать только, если начинается с «fw»  и содержит ровно две цифры после, то это указывается так:

$route = Hostname::factory(array(
    'route' => ':subdomain.domain.tld',
    'constraints' => array(
        'subdomain' => 'fwd{2}'
    ),
));

В выше приведенном примере будет возвращен только ключ «subdomain» из RouteMatch. Если же возникает необходимость добавления информации, по которой будет производиться поиск совпадений, при поиске маршрутов, или задать значение по умолчанию для возвращающегося поддомена, то задайте «defaults»:

$route = Hostname::factory(array(
    'route' => ':subdomain.domain.tld',
    'constraints' => array(
        'subdomain' => 'fwd{2}'
    ),
    'defaults' => array(
        'type' => 'json',
    ),
));

При совпадении в примере выше будут возвращаться два ключа из RouteMatch: «subdomain» и «type».

 


Zend\Mvc\Router\Http\Literal

 

Маршрут «Literal» предназначен, для точного совпадения с введенным путем URI. В конфигурации должно быть то, с чем должно совпасть при поиске. В секциях «defaults»  и настройки должны быть данные, которые будут возвращены при совпадении.

$route = Literal::factory(array(
    'route' => '/foo',
    'defaults' => array(
        'controller' => 'Application\Controller\IndexController',
        'action' => 'foo'
    ),
));

В примере выше, совпадение будет с путем «/foo», а вернется ключ «controller» в RouteMatch со значением «foo-index».

 


Zend\Mvc\Router\HttpMethod

 

Маршрут «Method» используется для задания метода «http» или «verb» в запросе при проверке на совпадение. Так же допускается задание нескольких типов через запятую, для множественного совпадения.

$route = Method::factory(array(
    'verb' => 'post,put',
    'defaults' => array(
        'controller' => 'Application\Controller\IndexController',
        'action' => 'form-submit'
    ),
));

В примере выше  совпадение будет при получении в запросе http «POST» или «PUT», а в  объект RouteMatch будет возвращен ключ «action» со значением «form-submit».

 


Zend\Mvc\Router\Http\Part

 

Маршрут « Part»  позволяет создавать дерево маршрутов, основываясь на сегментах пути в URI. Фактически расширяет TreeRouteStack.

 

Достаточно сложен для описания, поэтому приведем сразу пример:

$route = Part::factory(array(
    'route' => array(
        'type' => 'literal',
        'options' => array(
            'route' => '/',
            'defaults' => array(
                'controller' => 'Application\Controller\IndexController',
                'action' => 'index'
            )
        ),
    ),
    'route_plugins' => $routePlugins,
    'may_terminate' => true,
    'child_routes' => array(
        'blog' => array(
            'type' => 'literal',
            'options' => array(
                'route' => 'blog',
                'defaults' => array(
                    'controller' => 'Application\Controller\BlogController',
                    'action' => 'index'
                )
            ),
            'may_terminate' => true,
            'child_routes' => array(
                'rss' => array(
                    'type' => 'literal',
                    'options' => array(
                        'route' => '/rss',
                        'defaults' => array(
                            'action' => 'rss'
                        )
                    ),
                    'may_terminate' => true,
                    'child_routes' => array(
                        'subrss' => array(
                            'type' => 'literal',
                            'options' => array(
                                'route' => '/sub',
                                'defaults' => array(
                                    'action' => 'subrss'
                                )
                            )
                        )
                    )
                )
            )
        ),
        'forum' => array(
            'type' => 'literal',
            'options' => array(
                'route' => 'forum',
                'defaults' => array(
                    'controller' => 'Application\Controller\ForumController',
                    'action' => 'index'
                )
            )
        )
    )
));

Вышеописанное соответствует следующему:

- «/» - загрузит контроллер «Index» и действие «index»

- «/blog» - загрузит контроллер «Blog» и действие «index»

- «/blog/rss» - загрузит контроллер «Blog» и действие «rss»

- «/blog/rss/sub» - загрузит контроллер «Blog» и действие «subrss»

- «/forum» - загрузит контроллер «Forum» и действие «index»

 

Вы можете использовать любой тип маршрутов при описании маршрута «Part».

 

Важно: маршрута «Part» не предназначен для непосредственного использования. Когда вы добавляете определения в «child_routes» с любым типом маршрута, этот маршрут становится частью маршрута «Part». Вобщем, описание этого маршрута довольно проблематично словами, поэтому надеемся, что дальнейшие примеры помогут Вам понять все необходимое.

 

Важно: В предыдущем примере $routePlugins представляет собой экземпляр Zend\Mvc\Router\RoutePluginManager.

$routePlugins = new Zend\Mvc\Router\RoutePluginManager();
$plugins = array(
    'hostname' => 'ZendMvcHttpRouteHostname',
    'literal' => 'ZendMvcHttpRouteLiteral',
    'part' => 'ZendMvcHttpRoutePart',
    'regex' => 'ZendMvcHttpRouteRegex',
    'scheme' => 'ZendMvcHttpRouteScheme',
    'segment' => 'ZendMvcHttpRouteSegment',
    'wildcard' => 'ZendMvcHttpRouteWildcard',
    'query' => 'ZendMvcHttpRouteQuery',
    'method' => 'ZendMvcHttpRouteMethod'
);
$foreach ($plugins as $name => $class) {
    $routePlugins->setInvokableClass($name, $class);
}

При использовании Zend\Mvc\Router\Http\TreeRouteStack, по умолчанию используется RoutePluginManager. Поэтому Вам нет необходимости беспокоиться про автозагрузку стандартных HTTP маршрутов.

 


Zend\Mvc\Router\Http\Regex

 

Маршрут «Regex» использует регулярные выражения для сопоставления URI путем.  Допускается любое валидное регулярное выражение. Мы так же рекомендуем использовать известные капчи (use named captures) для всех значеий, возвращающихся в RouteMatch.

 

В силу того, что регулярные выражения в маршрутах часто бывают довольно сложны, Вам необходимо будет использовать параметр «spec» или «specification» для сборки URL адреса из маршрута «Regex». «spec» -  это просто строка, замена осуществляется использованием «%keyname(имя_ключа)%» в строке, с ключами переданными из захваченных (captured) значений, или проименованных параметров, переданных в метод  assemble().

 

Как и в других маршрутах Вы можете использовать сегмент «defaults». Пример:

$route = Regex::factory(array(
    'regex' => '/blog/(?<id>[a-zA-Z0-9_-]+)(.(?<format>(json|html|xml|rss)))?',
    'defaults' => array(
        'controller' => 'Application\Controller\BlogController',
        'action'     => 'view',
        'format'     => 'html',
    ),
    'spec' => '/blog/%id%.%format%',
));

В примере выше совпадение будет с таким адресом или подобным ему: «/blog/001-some-blog_slug-here.html». В RouteMatch вернется четыре элемента: «id», «controller», «action», «format». При сборке URL из маршрута, значения «id» и «format» будут использованы для заполнения сегмента «spec».

 


Zend\Mvc\Router\Http\Scheme

 

Маршрут «Scheme» соответствует только схеме URI. Как и маршрут «Literal», Вы просто вводите те значения, которым точно должен соответствовать введенный адрес. Как и в других маршрутах, у Вас есть возможность использовать секцию «defaults».

$route = Scheme::factory(array(
    'scheme' => 'https',
    'defaults' => array(
        'https' => true,
    ),
));

В примере выше  совпадение пройдет только, если будет указана схема «https», а в RouteMatch вернется ключ «https» со значением «true».

 


Zend\Mvc\Router\Http\Segment

 

Маршрут «Segment» позволяет находить соответствия по сегментам в URI  путях.  Сегменты указываются через двоеточие « : »,  за которыми следуют буквенно-цифровые символы. Если семент не является обязательным, то он заключается в квадратные скобочки « [ ] ».  Например, такой маске «/:foo[/:bar]» соответствует, что совпадение будет при введенных любых символах после « / », и эти символы будут назначены как значение ключу «foo». Если же далее будет символ « / », а после него юбые текстовые сиволы, то они будут использованы как значение для ключа «bar».

 

Разделителем между буквами и названиями сегментов может  быть что угодно. Например, можно записать так: «/:foo{-}[-:bar]». «{-}» после  «:foo» означает, что  далее может быть один или более разделителей.

 

Каждый сегмент может иметь свои ограничения. Каждое ограничение должно содержать  регулярное выражение, которому  должен будет соответствовать введенный адрес.

 

Так же можете использовать сегмент «defaults» при необходимости.

 

Обобщенный пример:

$route = Segment::factory(array(
    'route' => '/:controller[/:action]',
    'constraints' => array(
        'controller' => '[a-zA-Z][a-zA-Z0-9_-]+',
        'action'     => '[a-zA-Z][a-zA-Z0-9_-]+',
    ),
    'defaults' => array(
        'controller' => 'Application\Controller\IndexController',
        'action'     => 'index',
    ),
));


Zend\Mvc\Router\Http\Query

 

Маршрут «Query» позволяет определять и получать параметры (GET/query) для данного маршрута.

 

Особенность заключается в том, что Вы не создаете отдельного маршрута «Query», а добавляете его как дочерний маршрут к уже имеющемуся.

 

Пример использование:

$route = Part::factory(array(
    'route' => array(
        'type'    => 'literal',
        'options' => array(
            'route'    => 'page',
            'defaults' => array(
            ),
        ),
    ),
    'may_terminate' => true,
    'route_plugins'  => $routePlugins,
    'child_routes'  => array(
        'query' => array(
            'type' => 'Query',
            'options' => array(
                'defaults' => array(
                    'foo' => 'bar'
                )
            )
        ),
    ),
));

Как видите, он расположен в секции «query», которая и отвечает за задание параметров. Далее Вы можете использовать помощник вида  для задания строки параметров GET таким образом:

$this->url(
    'page/query',
    array(
        'name'=>'my-test-page',
        'format' => 'rss',
        'limit' => 10,
    )
);

Тоесть, вы должны добавить строку «/query» к имени маршрута, что б потом можно было использовать строку параметров.

 

В примере выше, в маршруте «page» имеется только один параметр GET  с именем «name»  ( /page[/:name] ),  а остальные параметры «format» и «limit» будут добавлены как строка параметров.

 

Наш пример в итоге выведет следующее:  /page/my-test-page?format=rss&limit=10.

 


Примеры HTTP маршрутизации

 

Основная часть настройки маршрутизации объявляется в файле настройки модуля.

 

Два маршрута «literal» :

return array(
    'router' => array(
        'routes' => array(
            // Literal route named "home"
            'home' => array(
                'type' => 'literal',
                'options' => array(
                    'route' => '/',
                    'defaults' => array(
                        'controller' => 'Application\Controller\IndexController',
                        'action' => 'index'
                    )
                )
            ),
            // Literal route named "contact"
            'contact' => array(
                'type' => 'literal',
                'options' => array(
                    'route' => 'contact',
                    'defaults' => array(
                        'controller' => 'Application\Controller\ContactController',
                        'action' => 'form'
                    )
                )
            )
        )
    )
);

Комплексный пример с дочерними маршрутами:

return array(
    'router' => array(
        'routes' => array(
            // Literal route named "home"
            'home' => array(
                'type' => 'literal',
                'options' => array(
                    'route' => '/',
                    'defaults' => array(
                        'controller' => 'Application\Controller\IndexController',
                        'action' => 'index'
                    )
                )
            ),
            // Literal route named "blog", with child routes
            'blog' => array(
                'type' => 'literal',
                'options' => array(
                    'route' => '/blog',
                    'defaults' => array(
                        'controller' => 'Applicaton\Controller\BlogController',
                        'action' => 'index'
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    // Segment route for viewing one blog post
                    'post' => array(
                        'type' => 'segment',
                        'options' => array(
                            'route' => '/[:slug]',
                            'constraints' => array(
                                'slug' => '[a-zA-Z0-9_-]+'
                            ),
                            'defaults' => array(
                                'action' => 'view'
                            )
                        )
                    ),
                    // Literal route for viewing blog RSS feed
                    'rss' => array(
                        'type' => 'literal',
                        'options' => array(
                            'route' => '/rss',
                            'defaults' => array(
                                'action' => 'rss'
                            )
                        )
                    )
                )
            )
        )
    )
);

Использование дочерних маршрутов из приведенного примера осуществляется следующим образом:

echo $this->url('blog'); // gives "/blog"
echo $this->url('blog/post', array('slug' => 'my-post')); // gives "/blog/my-post"
echo $this->url('blog/rss'); // gives "/blog/rss"


Warning!

При использовании дочерних маршрутов, обратите внимание, что  секции «may_terminate» и «child_routes» находятся на одном уровне, так же как и секции «type» и «options».

 


Console Route Types

 

Zend Framework 2.0 так же позволяет создавать маршруты для консоли. Более подробно про них можно почитать в разделе «Console» (консоль).  


Автор статьи: DuB