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

Commit 94ee452

Browse files
committed
Merge branch 'feature/139' into develop
Close #139
2 parents b554c47 + 167f858 commit 94ee452

File tree

4 files changed

+63
-91
lines changed

4 files changed

+63
-91
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ for full details on how to migrate your v2 application.
116116
`Zend\Mvc\Service\ServiceManagerConfig` and
117117
`Zend\Mvc\Controller\ControllerManager`. You will need to inject your
118118
dependencies specifically going forward.
119+
- [#139](https://github.com/zendframework/zend-mvc/pull/139) removes support for
120+
pseudo-module template resolution using the `__NAMESPACE__` routing
121+
configuration option, as it often led to conflicts when multiple modules
122+
shared a common top-level namespace. Auto-resolution now always takes into
123+
account the full namespace (minus the `Controller` segment).
119124

120125
### Fixed
121126

doc/book/migration/to-v3-0.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,35 @@ $ composer require zendframework/zend-validator
410410
Note: the above assumes you have already installed zend-component-installer, per
411411
the section above on [dependency reduction](#dependency-reduction).
412412

413+
## Zend\Mvc\View\InjectTemplateListener
414+
415+
The `InjectTemplateListener` attempts to map a controller *service* name to a
416+
template using a variety of heuristics, including an explicit map provided
417+
during configuration, or auto-detection based on the service name.
418+
419+
In version 2, the autodetection took into consideration the `__NAMESPACE__`
420+
provided in routing configuration, and would omit the module subnamespace if a
421+
match was found. This caused issues when multiple modules shared a top-level
422+
namespace (e.g., `ZF\Apigility` and `ZF\Apigility\Admin`) if each had a
423+
controller with the same name.
424+
425+
To avoid naming conflicts, version 3 removes this aspect of autodetection, and
426+
instead provides exactly one workflow for mapping:
427+
428+
- Strip the `Controller` subnamespace, if present (e.g.,
429+
the namespace `Application\Controller\\` is normalized to
430+
`Application\\`).
431+
- Strip the `Controller` suffix in the class name, if present (e.g.,
432+
`IndexController` is normalized to `Index`).
433+
- Inflect CamelCasing to dash-separated (e.g., `ShowUsers` becomes
434+
`show-users`).
435+
- Replace the namespace separator with a slash.
436+
437+
As a full example, the controller service name
438+
`TestSomething\With\Controller\CamelCaseController` will always map to
439+
`test-something/with/camel-case`, regardless of the `__NAMESPACE__` value
440+
provided in routing configuration.
441+
413442
## Zend\Mvc\View\SendResponseListener
414443

415444
`Zend\Mvc\View\SendResponseListener` was deprecated with the 2.2 release, and

src/View/Http/InjectTemplateListener.php

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use Zend\EventManager\AbstractListenerAggregate;
1313
use Zend\EventManager\EventManagerInterface as Events;
1414
use Zend\Mvc\MvcEvent;
15-
use Zend\Mvc\ModuleRouteListener;
1615
use Zend\Stdlib\StringUtils;
1716
use Zend\View\Model\ModelInterface as ViewModel;
1817

@@ -73,28 +72,6 @@ public function injectTemplate(MvcEvent $e)
7372
}
7473

7574
$template = $this->mapController($controller);
76-
if (!$template) {
77-
$module = $this->deriveModuleNamespace($controller);
78-
79-
if ($namespace = $routeMatch->getParam(ModuleRouteListener::MODULE_NAMESPACE)) {
80-
$controllerSubNs = $this->deriveControllerSubNamespace($namespace);
81-
if (!empty($controllerSubNs)) {
82-
if (!empty($module)) {
83-
$module .= '/' . $controllerSubNs;
84-
} else {
85-
$module = $controllerSubNs;
86-
}
87-
}
88-
}
89-
90-
$controller = $this->deriveControllerClass($controller);
91-
$template = $this->inflectName($module);
92-
93-
if (!empty($template)) {
94-
$template .= '/';
95-
}
96-
$template .= $this->inflectName($controller);
97-
}
9875

9976
$action = $routeMatch->getParam('action');
10077
if (null !== $action) {
@@ -124,10 +101,7 @@ public function setControllerMap(array $map)
124101
*/
125102
public function mapController($controller)
126103
{
127-
if (! is_string($controller)) {
128-
return false;
129-
}
130-
104+
$mapped = '';
131105
foreach ($this->controllerMap as $namespace => $replacement) {
132106
if (// Allow disabling rule by setting value to false since config
133107
// merging have no feature to remove entries
@@ -138,27 +112,26 @@ public function mapController($controller)
138112
continue;
139113
}
140114

141-
$map = '';
142115
// Map namespace to $replacement if its value is string
143116
if (is_string($replacement)) {
144-
$map = rtrim($replacement, '/') . '/';
117+
$mapped = rtrim($replacement, '/') . '/';
145118
$controller = substr($controller, strlen($namespace) + 1) ?: '';
119+
break;
146120
}
121+
}
147122

148-
//strip Controller namespace(s) (but not classname)
149-
$parts = explode('\\', $controller);
150-
array_pop($parts);
151-
$parts = array_diff($parts, ['Controller']);
152-
//strip trailing Controller in class name
153-
$parts[] = $this->deriveControllerClass($controller);
154-
$controller = implode('/', $parts);
123+
//strip Controller namespace(s) (but not classname)
124+
$parts = explode('\\', $controller);
125+
array_pop($parts);
126+
$parts = array_diff($parts, ['Controller']);
127+
//strip trailing Controller in class name
128+
$parts[] = $this->deriveControllerClass($controller);
129+
$controller = implode('/', $parts);
155130

156-
$template = trim($map . $controller, '/');
131+
$template = trim($mapped . $controller, '/');
157132

158-
// inflect CamelCase to dash
159-
return $this->inflectName($template);
160-
}
161-
return false;
133+
// inflect CamelCase to dash
134+
return $this->inflectName($template);
162135
}
163136

164137
/**
@@ -183,40 +156,6 @@ protected function inflectName($name)
183156
return strtolower($name);
184157
}
185158

186-
/**
187-
* Determine the top-level namespace of the controller
188-
*
189-
* @param string $controller
190-
* @return string
191-
*/
192-
protected function deriveModuleNamespace($controller)
193-
{
194-
if (!strstr($controller, '\\')) {
195-
return '';
196-
}
197-
$module = substr($controller, 0, strpos($controller, '\\'));
198-
return $module;
199-
}
200-
201-
/**
202-
* @param $namespace
203-
* @return string
204-
*/
205-
protected function deriveControllerSubNamespace($namespace)
206-
{
207-
if (!strstr($namespace, '\\')) {
208-
return '';
209-
}
210-
$nsArray = explode('\\', $namespace);
211-
212-
// Remove the first two elements representing the module and controller directory.
213-
$subNsArray = array_slice($nsArray, 2);
214-
if (empty($subNsArray)) {
215-
return '';
216-
}
217-
return implode('/', $subNsArray);
218-
}
219-
220159
/**
221160
* Determine the name of the controller
222161
*

test/View/InjectTemplateListenerTest.php

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@ public function testBypassesTemplateInjectionIfResultViewModelAlreadyHasATemplat
105105
$this->assertEquals('custom', $model->getTemplate());
106106
}
107107

108+
public function testMapsSubNamespaceToSubDirectory()
109+
{
110+
$myViewModel = new ViewModel();
111+
$myController = new \ZendTest\Mvc\Controller\TestAsset\SampleController();
112+
$this->event->setTarget($myController);
113+
$this->event->setResult($myViewModel);
114+
115+
$this->listener->injectTemplate($this->event);
116+
117+
$this->assertEquals('zend-test/mvc/test-asset/sample', $myViewModel->getTemplate());
118+
}
119+
108120
public function testMapsSubNamespaceToSubDirectoryWithControllerFromRouteMatch()
109121
{
110122
$this->routeMatch->setParam(ModuleRouteListener::MODULE_NAMESPACE, 'Aj\Controller\SweetAppleAcres\Reports');
@@ -134,7 +146,7 @@ public function testMapsSubNamespaceToSubDirectoryWithControllerFromRouteMatchHa
134146
$this->event->setResult($model);
135147
$this->listener->injectTemplate($this->event);
136148

137-
$this->assertEquals('aj/sweet-apple-acres/reports/cider-sales/pinkie-pie-revenue', $model->getTemplate());
149+
$this->assertEquals('aj/sweet-apple-acres/reports/sub/cider-sales/pinkie-pie-revenue', $model->getTemplate());
138150
}
139151

140152
public function testMapsSubNamespaceToSubDirectoryWithControllerFromEventTarget()
@@ -152,7 +164,7 @@ public function testMapsSubNamespaceToSubDirectoryWithControllerFromEventTarget(
152164
$this->event->setResult($myViewModel);
153165
$this->listener->injectTemplate($this->event);
154166

155-
$this->assertEquals('zend-test/controller/test-asset/sample/test', $myViewModel->getTemplate());
167+
$this->assertEquals('zend-test/mvc/test-asset/sample/test', $myViewModel->getTemplate());
156168
}
157169

158170
public function testMapsSubNamespaceToSubDirectoryWithControllerFromEventTargetShouldMatchControllerFromRouteParam()
@@ -201,19 +213,6 @@ public function testControllerMatchedByMapIsInflected()
201213
$this->assertEquals('zend-test/mvc/test-asset/sample', $myViewModel->getTemplate());
202214
}
203215

204-
public function testControllerNotMatchedByMapIsNotAffected()
205-
{
206-
$this->routeMatch->setParam('action', 'test');
207-
$myViewModel = new ViewModel();
208-
$myController = new \ZendTest\Mvc\Controller\TestAsset\SampleController();
209-
210-
$this->event->setTarget($myController);
211-
$this->event->setResult($myViewModel);
212-
$this->listener->injectTemplate($this->event);
213-
214-
$this->assertEquals('zend-test/sample/test', $myViewModel->getTemplate());
215-
}
216-
217216
public function testFullControllerNameMatchIsMapped()
218217
{
219218
$this->listener->setControllerMap([
@@ -339,6 +338,6 @@ public function testPrefersRouteMatchController()
339338
$this->event->setResult($myViewModel);
340339
$this->listener->injectTemplate($this->event);
341340

342-
$this->assertEquals('some/sample', $myViewModel->getTemplate());
341+
$this->assertEquals('some/other/service/namespace/sample', $myViewModel->getTemplate());
343342
}
344343
}

0 commit comments

Comments
 (0)