3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Mvc\Router\Http
;
14 use Zend\Mvc\Router\Exception
;
15 use Zend\Mvc\Router\SimpleRouteStack
;
16 use Zend\Stdlib\ArrayUtils
;
17 use Zend\Stdlib\RequestInterface
as Request
;
18 use Zend\Uri\Http
as HttpUri
;
21 * Tree search implementation.
23 class TreeRouteStack
extends SimpleRouteStack
37 protected $requestUri;
42 * We use an ArrayObject in this case so we can easily pass it down the tree
47 protected $prototypes;
50 * factory(): defined by RouteInterface interface.
52 * @see \Zend\Mvc\Router\RouteInterface::factory()
53 * @param array|Traversable $options
54 * @return SimpleRouteStack
55 * @throws Exception\InvalidArgumentException
57 public static function factory($options = array())
59 if ($options instanceof Traversable
) {
60 $options = ArrayUtils
::iteratorToArray($options);
61 } elseif (!is_array($options)) {
62 throw new Exception\
InvalidArgumentException(__METHOD__
. ' expects an array or Traversable set of options');
65 $instance = parent
::factory($options);
67 if (isset($options['prototypes'])) {
68 $instance->addPrototypes($options['prototypes']);
75 * init(): defined by SimpleRouteStack.
77 * @see SimpleRouteStack::init()
79 protected function init()
81 $this->prototypes
= new ArrayObject
;
83 $routes = $this->routePluginManager
;
85 'chain' => __NAMESPACE__
. '\Chain',
86 'hostname' => __NAMESPACE__
. '\Hostname',
87 'literal' => __NAMESPACE__
. '\Literal',
88 'method' => __NAMESPACE__
. '\Method',
89 'part' => __NAMESPACE__
. '\Part',
90 'query' => __NAMESPACE__
. '\Query',
91 'regex' => __NAMESPACE__
. '\Regex',
92 'scheme' => __NAMESPACE__
. '\Scheme',
93 'segment' => __NAMESPACE__
. '\Segment',
94 'wildcard' => __NAMESPACE__
. '\Wildcard',
97 $routes->setInvokableClass($name, $class);
102 * addRoute(): defined by RouteStackInterface interface.
104 * @see RouteStackInterface::addRoute()
105 * @param string $name
106 * @param mixed $route
107 * @param int $priority
108 * @return TreeRouteStack
110 public function addRoute($name, $route, $priority = null)
112 if (!$route instanceof RouteInterface
) {
113 $route = $this->routeFromArray($route);
116 return parent
::addRoute($name, $route, $priority);
120 * routeFromArray(): defined by SimpleRouteStack.
122 * @see SimpleRouteStack::routeFromArray()
123 * @param string|array|Traversable $specs
124 * @return RouteInterface
125 * @throws Exception\InvalidArgumentException When route definition is not an array nor traversable
126 * @throws Exception\InvalidArgumentException When chain routes are not an array nor traversable
127 * @throws Exception\RuntimeException When a generated routes does not implement the HTTP route interface
129 protected function routeFromArray($specs)
131 if (is_string($specs)) {
132 if (null === ($route = $this->getPrototype($specs))) {
133 throw new Exception\
RuntimeException(sprintf('Could not find prototype with name %s', $specs));
137 } elseif ($specs instanceof Traversable
) {
138 $specs = ArrayUtils
::iteratorToArray($specs);
139 } elseif (!is_array($specs)) {
140 throw new Exception\
InvalidArgumentException('Route definition must be an array or Traversable object');
143 if (isset($specs['chain_routes'])) {
144 if (!is_array($specs['chain_routes'])) {
145 throw new Exception\
InvalidArgumentException('Chain routes must be an array or Traversable object');
148 $chainRoutes = array_merge(array($specs), $specs['chain_routes']);
149 unset($chainRoutes[0]['chain_routes']);
152 'routes' => $chainRoutes,
153 'route_plugins' => $this->routePluginManager
,
154 'prototypes' => $this->prototypes
,
157 $route = $this->routePluginManager
->get('chain', $options);
159 $route = parent
::routeFromArray($specs);
162 if (!$route instanceof RouteInterface
) {
163 throw new Exception\
RuntimeException('Given route does not implement HTTP route interface');
166 if (isset($specs['child_routes'])) {
169 'may_terminate' => (isset($specs['may_terminate']) && $specs['may_terminate']),
170 'child_routes' => $specs['child_routes'],
171 'route_plugins' => $this->routePluginManager
,
172 'prototypes' => $this->prototypes
,
175 $priority = (isset($route->priority
) ?
$route->priority
: null);
177 $route = $this->routePluginManager
->get('part', $options);
178 $route->priority
= $priority;
185 * Add multiple prototypes at once.
187 * @param Traversable $routes
188 * @return TreeRouteStack
189 * @throws Exception\InvalidArgumentException
191 public function addPrototypes($routes)
193 if (!is_array($routes) && !$routes instanceof Traversable
) {
194 throw new Exception\
InvalidArgumentException('addPrototypes expects an array or Traversable set of routes');
197 foreach ($routes as $name => $route) {
198 $this->addPrototype($name, $route);
207 * @param string $name
208 * @param mixed $route
209 * @return TreeRouteStack
211 public function addPrototype($name, $route)
213 if (!$route instanceof RouteInterface
) {
214 $route = $this->routeFromArray($route);
217 $this->prototypes
[$name] = $route;
225 * @param string $name
226 * @return RouteInterface|null
228 public function getPrototype($name)
230 if (isset($this->prototypes
[$name])) {
231 return $this->prototypes
[$name];
238 * match(): defined by \Zend\Mvc\Router\RouteInterface
240 * @see \Zend\Mvc\Router\RouteInterface::match()
241 * @param Request $request
242 * @param integer|null $pathOffset
243 * @param array $options
244 * @return RouteMatch|null
246 public function match(Request
$request, $pathOffset = null, array $options = array())
248 if (!method_exists($request, 'getUri')) {
252 if ($this->baseUrl
=== null && method_exists($request, 'getBaseUrl')) {
253 $this->setBaseUrl($request->getBaseUrl());
256 $uri = $request->getUri();
257 $baseUrlLength = strlen($this->baseUrl
) ?
: null;
259 if ($pathOffset !== null) {
260 $baseUrlLength +
= $pathOffset;
263 if ($this->requestUri
=== null) {
264 $this->setRequestUri($uri);
267 if ($baseUrlLength !== null) {
268 $pathLength = strlen($uri->getPath()) - $baseUrlLength;
273 foreach ($this->routes
as $name => $route) {
275 ($match = $route->match($request, $baseUrlLength, $options)) instanceof RouteMatch
276 && ($pathLength === null ||
$match->getLength() === $pathLength)
278 $match->setMatchedRouteName($name);
280 foreach ($this->defaultParams
as $paramName => $value) {
281 if ($match->getParam($paramName) === null) {
282 $match->setParam($paramName, $value);
294 * assemble(): defined by \Zend\Mvc\Router\RouteInterface interface.
296 * @see \Zend\Mvc\Router\RouteInterface::assemble()
297 * @param array $params
298 * @param array $options
300 * @throws Exception\InvalidArgumentException
301 * @throws Exception\RuntimeException
303 public function assemble(array $params = array(), array $options = array())
305 if (!isset($options['name'])) {
306 throw new Exception\
InvalidArgumentException('Missing "name" option');
309 $names = explode('/', $options['name'], 2);
310 $route = $this->routes
->get($names[0]);
313 throw new Exception\
RuntimeException(sprintf('Route with name "%s" not found', $names[0]));
316 if (isset($names[1])) {
317 if (!$route instanceof TreeRouteStack
) {
318 throw new Exception\
RuntimeException(sprintf('Route with name "%s" does not have child routes', $names[0]));
320 $options['name'] = $names[1];
322 unset($options['name']);
325 if (isset($options['only_return_path']) && $options['only_return_path']) {
326 return $this->baseUrl
. $route->assemble(array_merge($this->defaultParams
, $params), $options);
329 if (!isset($options['uri'])) {
330 $uri = new HttpUri();
332 if (isset($options['force_canonical']) && $options['force_canonical']) {
333 if ($this->requestUri
=== null) {
334 throw new Exception\
RuntimeException('Request URI has not been set');
337 $uri->setScheme($this->requestUri
->getScheme())
338 ->setHost($this->requestUri
->getHost())
339 ->setPort($this->requestUri
->getPort());
342 $options['uri'] = $uri;
344 $uri = $options['uri'];
347 $path = $this->baseUrl
. $route->assemble(array_merge($this->defaultParams
, $params), $options);
349 if (isset($options['query'])) {
350 $uri->setQuery($options['query']);
353 if (isset($options['fragment'])) {
354 $uri->setFragment($options['fragment']);
357 if ((isset($options['force_canonical']) && $options['force_canonical']) ||
$uri->getHost() !== null ||
$uri->getScheme() !== null) {
358 if (($uri->getHost() === null ||
$uri->getScheme() === null) && $this->requestUri
=== null) {
359 throw new Exception\
RuntimeException('Request URI has not been set');
362 if ($uri->getHost() === null) {
363 $uri->setHost($this->requestUri
->getHost());
366 if ($uri->getScheme() === null) {
367 $uri->setScheme($this->requestUri
->getScheme());
370 return $uri->setPath($path)->normalize()->toString();
371 } elseif (!$uri->isAbsolute() && $uri->isValidRelative()) {
372 return $uri->setPath($path)->normalize()->toString();
381 * @param string $baseUrl
384 public function setBaseUrl($baseUrl)
386 $this->baseUrl
= rtrim($baseUrl, '/');
395 public function getBaseUrl()
397 return $this->baseUrl
;
401 * Set the request URI.
403 * @param HttpUri $uri
404 * @return TreeRouteStack
406 public function setRequestUri(HttpUri
$uri)
408 $this->requestUri
= $uri;
413 * Get the request URI.
417 public function getRequestUri()
419 return $this->requestUri
;