Skip to content
This repository was archived by the owner on Jan 31, 2020. It is now read-only.

Commit 24de85a

Browse files
committed
Merge branch 'feature/50' into develop
Close #50
2 parents d4faed9 + dc0c5c2 commit 24de85a

File tree

4 files changed

+329
-1
lines changed

4 files changed

+329
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ All notable changes to this project will be documented in this file, in reverse
66

77
### Added
88

9-
- Nothing.
9+
- [#50](https://github.com/zendframework/zend-router/pull/50) adds `Zend\Router\Http\Placeholder`, which can be used within reusable
10+
modules to indicate a route with child routes where the root route may be
11+
overridden. By default, the `Placeholder` route always matches, passing on
12+
further matching to the defined child routes.
1013

1114
### Changed
1215

docs/book/routing.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,76 @@ You may use any route type as a child route of a `Part` route.
352352
> set up by default, and the developer does not need to worry about autoloading
353353
> of standard HTTP routes.
354354
355+
### Zend\\Router\\Http\\Placeholder
356+
357+
- **Since 3.2.0**
358+
359+
A `Placeholder` route is provided for use by reusable modules. The idea is that a
360+
module can provide a set of routes anchored by a placeholder route type. The end
361+
consumer can replace this placeholder route with a different route type of their
362+
choosing to customise how the module's routes act within the application as a
363+
whole, without needing to alter either the route configuration of the module or
364+
the URL building contained within the module.
365+
366+
As an example, consider a reusable user module which provides routing configuration
367+
for login and registration pages. A consumer of this module may want the auth module
368+
to live either:
369+
370+
1) At the root of their domain.
371+
2) Under a path, e.g. `/auth/`.
372+
3) On a separate subdomain, e.g. `auth.mydomain.com`.
373+
374+
The module can provide configuration such as the following:
375+
376+
```php
377+
return [
378+
'auth' => [
379+
'type' => \Zend\Mvc\Router\Http\Placeholder::class,
380+
'child_routes' => [
381+
'login' => [
382+
'type' => \Zend\Mvc\Router\Http\Literal::class,
383+
'options' => [
384+
'route' => '/login',
385+
'defaults' => [
386+
'controller' => AuthController::class,
387+
'action' => 'login'
388+
],
389+
],
390+
],
391+
'register' => [
392+
'type' => \Zend\Mvc\Router\Http\Literal::class,
393+
'options' => [
394+
'route' => '/register',
395+
'defaults' => [
396+
'controller' => RegistrationController::class,
397+
'action' => 'register'
398+
],
399+
],
400+
],
401+
],
402+
],
403+
];
404+
```
405+
406+
The consuming application can then leave this configuration as is to have the
407+
auth module sit at the route of their domain. If they wish to change the
408+
resource location, they can provide an alternative route type to replace the
409+
`Placeholder` route as part of their own router configuration. As an example:
410+
411+
```php
412+
return [
413+
'auth' => [
414+
'type' => \Zend\Mvc\Router\Http\Literal::class,
415+
'options' => [
416+
'route' => '/auth',
417+
],
418+
],
419+
];
420+
```
421+
422+
In the above, the top-level route type changes from `Placeholder` to `Literal`,
423+
and the routes will now match against `/auth/login` and `/auth/register`.
424+
355425
### Zend\\Router\\Http\\Regex
356426

357427
A `Regex` route utilizes a regular expression to match against the URI path. Any

src/Http/Placeholder.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
/**
3+
* @link https://github.com/zendframework/zend-router for the canonical source repository
4+
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5+
* @license https://framework.zend.com/license/new-bsd New BSD License
6+
*/
7+
8+
namespace Zend\Router\Http;
9+
10+
use Traversable;
11+
use Zend\Router\Exception;
12+
use Zend\Stdlib\ArrayUtils;
13+
use Zend\Stdlib\RequestInterface as Request;
14+
15+
/**
16+
* Placeholder route.
17+
*/
18+
class Placeholder implements RouteInterface
19+
{
20+
private $defaults;
21+
22+
public function __construct(array $defaults)
23+
{
24+
$this->defaults = $defaults;
25+
}
26+
27+
/**
28+
* factory(): defined by RouteInterface interface.
29+
*
30+
* @see \Zend\Router\RouteInterface::factory()
31+
* @param array|Traversable $options
32+
* @return Placeholder
33+
* @throws Exception\InvalidArgumentException
34+
*/
35+
public static function factory($options = [])
36+
{
37+
if ($options instanceof Traversable) {
38+
$options = ArrayUtils::iteratorToArray($options);
39+
}
40+
41+
if (! is_array($options)) {
42+
throw new Exception\InvalidArgumentException(sprintf(
43+
'%s expects an array or Traversable set of options',
44+
__METHOD__
45+
));
46+
}
47+
48+
if (! isset($options['defaults'])) {
49+
$options['defaults'] = [];
50+
}
51+
52+
if (! is_array($options['defaults'])) {
53+
throw new Exception\InvalidArgumentException('options[defaults] expected to be an array if set');
54+
}
55+
56+
return new static($options['defaults']);
57+
}
58+
59+
/**
60+
* match(): defined by RouteInterface interface.
61+
*
62+
* @see \Zend\Router\RouteInterface::match()
63+
* @param Request $request
64+
* @param integer|null $pathOffset
65+
* @return RouteMatch|null
66+
*/
67+
public function match(Request $request, $pathOffset = null)
68+
{
69+
return new RouteMatch($this->defaults);
70+
}
71+
72+
/**
73+
* assemble(): Defined by RouteInterface interface.
74+
*
75+
* @see \Zend\Router\RouteInterface::assemble()
76+
* @param array $params
77+
* @param array $options
78+
* @return mixed
79+
*/
80+
public function assemble(array $params = [], array $options = [])
81+
{
82+
return '';
83+
}
84+
85+
/**
86+
* getAssembledParams(): defined by RouteInterface interface.
87+
*
88+
* @see RouteInterface::getAssembledParams
89+
* @return array
90+
*/
91+
public function getAssembledParams()
92+
{
93+
return [];
94+
}
95+
}

test/Http/PlaceholderTest.php

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
/**
3+
* @link https://github.com/zendframework/zend-router for the canonical source repository
4+
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5+
* @license https://framework.zend.com/license/new-bsd New BSD License
6+
*/
7+
8+
namespace ZendTest\Router\Http;
9+
10+
use PHPUnit\Framework\TestCase;
11+
use Zend\Http\Request;
12+
use Zend\Router\Http\Hostname;
13+
use Zend\Router\Http\Literal;
14+
use Zend\Router\Http\Placeholder;
15+
use Zend\Router\Http\RouteMatch;
16+
use Zend\Router\Http\TreeRouteStack;
17+
use Zend\Stdlib\ArrayUtils;
18+
use ZendTest\Router\FactoryTester;
19+
20+
class PlaceholderTest extends TestCase
21+
{
22+
private static $routeConfig = [
23+
'auth' => [
24+
'type' => Placeholder::class,
25+
'child_routes' => [
26+
'login' => [
27+
'type' => Literal::class,
28+
'options' => [
29+
'route' => '/',
30+
'defaults' => [
31+
'controller' => 'AuthController',
32+
'action' => 'login'
33+
],
34+
],
35+
],
36+
'register' => [
37+
'type' => Literal::class,
38+
'options' => [
39+
'route' => '/register',
40+
'defaults' => [
41+
'controller' => 'RegistrationController',
42+
'action' => 'register'
43+
],
44+
],
45+
],
46+
],
47+
],
48+
];
49+
public function testMatch()
50+
{
51+
$route = new Placeholder([]);
52+
53+
$request = new Request();
54+
$request->setUri('http://example.com/');
55+
$match = $route->match($request);
56+
57+
$this->assertInstanceOf(RouteMatch::class, $match);
58+
}
59+
60+
public function testAssembling()
61+
{
62+
$route = new Placeholder([]);
63+
$this->assertEquals('', $route->assemble());
64+
}
65+
66+
public function testGetAssembledParams()
67+
{
68+
$route = new Placeholder([]);
69+
$this->assertEquals([], $route->getAssembledParams());
70+
}
71+
72+
public function testFactory()
73+
{
74+
$tester = new FactoryTester($this);
75+
$tester->testFactory(Placeholder::class, [], []);
76+
}
77+
78+
/**
79+
* @dataProvider placeholderProvider
80+
* @param array $additionalConfig
81+
* @param string $uri
82+
* @param string $expectedRouteName
83+
*/
84+
public function testPlaceholderDefault($additionalConfig, $uri, $expectedRouteName)
85+
{
86+
$routeConfig = ArrayUtils::merge(self::$routeConfig, $additionalConfig);
87+
$router = TreeRouteStack::factory(['routes' => $routeConfig]);
88+
89+
$request = new Request();
90+
$request->setUri($uri);
91+
$match = $router->match($request);
92+
93+
$this->assertInstanceOf(RouteMatch::class, $match);
94+
$this->assertEquals($expectedRouteName, $match->getMatchedRouteName());
95+
}
96+
97+
public function placeholderProvider()
98+
{
99+
$home = [
100+
'home' => [
101+
'type' => Literal::class,
102+
'options' => [
103+
'route' => '/home',
104+
'defaults' => [
105+
'controller' => 'HomeController',
106+
'action' => 'index'
107+
],
108+
],
109+
]
110+
];
111+
112+
$homeAtRootAuthMoved = [
113+
'home' => [
114+
'type' => Literal::class,
115+
'options' => [
116+
'route' => '/',
117+
'defaults' => [
118+
'controller' => 'HomeController',
119+
'action' => 'index'
120+
],
121+
],
122+
],
123+
'auth' => [
124+
'type' => Literal::class,
125+
'options' => ['route' => '/auth']
126+
]
127+
];
128+
129+
$homeAtRootAuthOnSubDomain = [
130+
'home' => [
131+
'type' => Hostname::class,
132+
'options' => [
133+
'route' => 'example.com',
134+
'defaults' => [
135+
'controller' => 'HomeController',
136+
'action' => 'index'
137+
],
138+
],
139+
],
140+
'auth' => [
141+
'type' => Hostname::class,
142+
'options' => ['route' => 'auth.example.com']
143+
]
144+
];
145+
146+
// @codingStandardsIgnoreStart
147+
return [
148+
'no-override-login' => [$home, 'http://example.com/', 'auth/login'],
149+
'no-override-register' => [$home, 'http://example.com/register', 'auth/register'],
150+
'no-override-home' => [$home, 'http://example.com/home', 'home'],
151+
'path-override-login' => [$homeAtRootAuthMoved, 'http://example.com/auth/', 'auth/login'],
152+
'path-override-register' => [$homeAtRootAuthMoved, 'http://example.com/auth/register', 'auth/register'],
153+
'path-override-home' => [$homeAtRootAuthMoved, 'http://example.com', 'home'],
154+
'subdomain-override-login' => [$homeAtRootAuthOnSubDomain, 'http://auth.example.com/', 'auth/login'],
155+
'subdomain-override-register' => [$homeAtRootAuthOnSubDomain, 'http://auth.example.com/register', 'auth/register'],
156+
'subdomina-override-home' => [$homeAtRootAuthOnSubDomain, 'http://example.com/', 'home'],
157+
];
158+
// @codingStandardsIgnoreEnd
159+
}
160+
}

0 commit comments

Comments
 (0)