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

Commit 3739645

Browse files
committed
Ensure that helpers with override factories work for all aliases
This patch implements tests for each of the url, basepath, and doctype helpers, ensuring that the factory is invoked and the helper returned properly configured *for each alias* we define, as well as for the fully qualifed class name. Interestingly, while the basepath and doctype tests fail for every case, the URL helper only fails for the case of a fully qualified name; I'm hoping this will still work as a fix. I've refactored the `ViewHelperManagerFactory` substantially using the extract method refactor to extract: - helper factories (these are returned by dedicated methods now) - helper factory registration - helper configuration from other components In the case of the second bullet point, I am now registering the plugins using the FQCN as well as the normalized version of it, and not the aliases; the aliases will resolve to the factory name.
1 parent 9181d0e commit 3739645

File tree

3 files changed

+250
-43
lines changed

3 files changed

+250
-43
lines changed

src/Service/AbstractPluginManagerFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function createService(ServiceLocatorInterface $serviceLocator)
3131
/* @var $plugins AbstractPluginManager */
3232
$plugins = new $pluginManagerClass;
3333
$plugins->setServiceLocator($serviceLocator);
34-
$configuration = $serviceLocator->get('Config');
34+
$configuration = $serviceLocator->get('config');
3535

3636
if (isset($configuration['di']) && $serviceLocator->has('Di')) {
3737
$plugins->addAbstractFactory($serviceLocator->get('DiAbstractServiceFactory'));

src/Service/ViewHelperManagerFactory.php

Lines changed: 114 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@
99

1010
namespace Zend\Mvc\Service;
1111

12+
use Interop\Container\ContainerInterface;
1213
use Zend\Console\Console;
1314
use Zend\Mvc\Exception;
1415
use Zend\Mvc\Router\RouteMatch;
1516
use Zend\ServiceManager\ConfigInterface;
1617
use Zend\ServiceManager\ServiceLocatorInterface;
1718
use Zend\View\Helper as ViewHelper;
19+
use Zend\View\HelperPluginManager;
1820
use Zend\View\Helper\HelperInterface as ViewHelperInterface;
1921

2022
class ViewHelperManagerFactory extends AbstractPluginManagerFactory
2123
{
22-
const PLUGIN_MANAGER_CLASS = 'Zend\View\HelperPluginManager';
24+
const PLUGIN_MANAGER_CLASS = HelperPluginManager::class;
2325

2426
/**
2527
* An array of helper configuration classes to ensure are on the helper_map stack.
@@ -43,29 +45,92 @@ public function createService(ServiceLocatorInterface $serviceLocator)
4345
{
4446
$plugins = parent::createService($serviceLocator);
4547

48+
// Configure default helpers from other components
49+
$plugins = $this->configureHelpers($plugins);
50+
51+
// Override plugin factories
52+
$plugins = $this->injectOverrideFactories($plugins, $serviceLocator);
53+
54+
return $plugins;
55+
}
56+
57+
/**
58+
* Configure helpers from other components.
59+
*
60+
* Loops through the list of default helper configuration classes, and uses
61+
* each to configure the helper plugin manager.
62+
*
63+
* @param HelperPluginManager $plugins
64+
* @return HelperPluginManager
65+
*/
66+
private function configureHelpers(HelperPluginManager $plugins)
67+
{
4668
foreach ($this->defaultHelperMapClasses as $configClass) {
47-
if (is_string($configClass) && class_exists($configClass)) {
48-
$config = new $configClass;
49-
50-
if (!$config instanceof ConfigInterface) {
51-
throw new Exception\RuntimeException(sprintf(
52-
'Invalid service manager configuration class provided; received "%s", expected class implementing %s',
53-
$configClass,
54-
'Zend\ServiceManager\ConfigInterface'
55-
));
56-
}
57-
58-
$config->configureServiceManager($plugins);
69+
if (! is_string($configClass) || ! class_exists($configClass)) {
70+
continue;
71+
}
72+
73+
$config = new $configClass;
74+
75+
if (! $config instanceof ConfigInterface) {
76+
throw new Exception\RuntimeException(sprintf(
77+
'Invalid service manager configuration class provided; received "%s", expected class implementing %s',
78+
$configClass,
79+
'Zend\ServiceManager\ConfigInterface'
80+
));
5981
}
82+
83+
$config->configureServiceManager($plugins);
6084
}
6185

62-
// Configure URL view helper with router
63-
$plugins->setFactory('url', function () use ($serviceLocator) {
86+
return $plugins;
87+
}
88+
89+
/**
90+
* Inject override factories into the plugin manager.
91+
*
92+
* @param HelperPluginManager $plugins
93+
* @param ContainerInterface $services
94+
* @return HelperPluginManager
95+
*/
96+
private function injectOverrideFactories(HelperPluginManager $plugins, ContainerInterface $services)
97+
{
98+
// Configure URL view helper
99+
$urlFactory = $this->createUrlHelperFactory($services);
100+
$plugins->setFactory(ViewHelper\Url::class, $urlFactory);
101+
$plugins->setFactory('zendviewhelperurl', $urlFactory);
102+
103+
// Configure base path helper
104+
$basePathFactory = $this->createBasePathHelperFactory($services);
105+
$plugins->setFactory(ViewHelper\BasePath::class, $basePathFactory);
106+
$plugins->setFactory('zendviewhelperbasepath', $basePathFactory);
107+
108+
// Configure doctype view helper
109+
$doctypeFactory = $this->createDoctypeHelperFactory($services);
110+
$plugins->setFactory(ViewHelper\doctype::class, $doctypeFactory);
111+
$plugins->setFactory('zendviewhelperdoctype', $doctypeFactory);
112+
113+
return $plugins;
114+
}
115+
116+
/**
117+
* Create and return a factory for creating a URL helper.
118+
*
119+
* Retrieves the application and router from the servicemanager,
120+
* and the route match from the MvcEvent composed by the application,
121+
* using them to configure the helper.
122+
*
123+
* @param ContainerInterface $services
124+
* @return callable
125+
*/
126+
private function createUrlHelperFactory(ContainerInterface $services)
127+
{
128+
return function () use ($services) {
64129
$helper = new ViewHelper\Url;
65130
$router = Console::isConsole() ? 'HttpRouter' : 'Router';
66-
$helper->setRouter($serviceLocator->get($router));
131+
$helper->setRouter($services->get($router));
67132

68-
$match = $serviceLocator->get('application')
133+
$match = $services->get('application')
69134
->getMvcEvent()
70135
->getRouteMatch()
71136
;
@@ -75,10 +140,21 @@ public function createService(ServiceLocatorInterface $serviceLocator)
75140
}
76141

77142
return $helper;
78-
});
143+
};
144+
}
79145

80-
$plugins->setFactory('basepath', function () use ($serviceLocator) {
81-
$config = $serviceLocator->has('Config') ? $serviceLocator->get('Config') : [];
146+
/**
147+
* Create and return a factory for creating a BasePath helper.
148+
*
149+
* Uses configuration and request services to configure the helper.
150+
*
151+
* @param ContainerInterface $services
152+
* @return callable
153+
*/
154+
private function createBasePathHelperFactory(ContainerInterface $services)
155+
{
156+
return function () use ($services) {
157+
$config = $services->has('config') ? $services->get('config') : [];
82158
$basePathHelper = new ViewHelper\BasePath;
83159

84160
if (Console::isConsole()
@@ -96,31 +172,35 @@ public function createService(ServiceLocatorInterface $serviceLocator)
96172
return $basePathHelper;
97173
}
98174

99-
$request = $serviceLocator->get('Request');
175+
$request = $services->get('Request');
100176

101177
if (is_callable([$request, 'getBasePath'])) {
102178
$basePathHelper->setBasePath($request->getBasePath());
103179
}
104180

105181
return $basePathHelper;
106-
});
107-
108-
/**
109-
* Configure doctype view helper with doctype from configuration, if available.
110-
*
111-
* Other view helpers depend on this to decide which spec to generate their tags
112-
* based on. This is why it must be set early instead of later in the layout phtml.
113-
*/
114-
$plugins->setFactory('doctype', function () use ($serviceLocator) {
115-
$config = $serviceLocator->has('Config') ? $serviceLocator->get('Config') : [];
182+
};
183+
}
184+
185+
/**
186+
* Create and return a Doctype helper factory.
187+
*
188+
* Other view helpers depend on this to decide which spec to generate their tags
189+
* based on. This is why it must be set early instead of later in the layout phtml.
190+
*
191+
* @param ContainerInterface $services
192+
* @return callable
193+
*/
194+
private function createDoctypeHelperFactory(ContainerInterface $services)
195+
{
196+
return function () use ($services) {
197+
$config = $services->has('config') ? $services->get('config') : [];
116198
$config = isset($config['view_manager']) ? $config['view_manager'] : [];
117199
$doctypeHelper = new ViewHelper\Doctype;
118200
if (isset($config['doctype']) && $config['doctype']) {
119201
$doctypeHelper->setDoctype($config['doctype']);
120202
}
121203
return $doctypeHelper;
122-
});
123-
124-
return $plugins;
204+
};
125205
}
126206
}

test/Service/ViewHelperManagerFactoryTest.php

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@
1111

1212
use PHPUnit_Framework_TestCase as TestCase;
1313
use Zend\Console\Request as ConsoleRequest;
14+
use Zend\Http\PhpEnvironment\Request;
15+
use Zend\Mvc\Application;
16+
use Zend\Mvc\MvcEvent;
17+
use Zend\Mvc\Router\RouteMatch;
18+
use Zend\Mvc\Router\RouteStackInterface;
1419
use Zend\Mvc\Service\ViewHelperManagerFactory;
1520
use Zend\ServiceManager\ServiceManager;
21+
use Zend\View\Helper;
1622

1723
class ViewHelperManagerFactoryTest extends TestCase
1824
{
@@ -41,7 +47,7 @@ public function emptyConfiguration()
4147
*/
4248
public function testDoctypeFactoryDoesNotRaiseErrorOnMissingConfiguration($config)
4349
{
44-
$this->services->setService('Config', $config);
50+
$this->services->setService('config', $config);
4551
$manager = $this->factory->createService($this->services);
4652
$this->assertInstanceof('Zend\View\HelperPluginManager', $manager);
4753
$doctype = $manager->get('doctype');
@@ -50,7 +56,7 @@ public function testDoctypeFactoryDoesNotRaiseErrorOnMissingConfiguration($confi
5056

5157
public function testConsoleRequestsResultInSilentFailure()
5258
{
53-
$this->services->setService('Config', []);
59+
$this->services->setService('config', []);
5460
$this->services->setService('Request', new ConsoleRequest());
5561

5662
$manager = $this->factory->createService($this->services);
@@ -67,18 +73,139 @@ public function testConsoleRequestsResultInSilentFailure()
6773
*/
6874
public function testConsoleRequestWithBasePathConsole()
6975
{
70-
$this->services->setService('Config',
71-
[
72-
'view_manager' => [
73-
'base_path_console' => 'http://test.com'
74-
]
76+
$this->services->setService('config', [
77+
'view_manager' => [
78+
'base_path_console' => 'http://test.com'
7579
]
76-
);
80+
]);
7781
$this->services->setService('Request', new ConsoleRequest());
7882

7983
$manager = $this->factory->createService($this->services);
8084

8185
$basePath = $manager->get('basepath');
8286
$this->assertEquals('http://test.com', $basePath());
8387
}
88+
89+
public function urlHelperNames()
90+
{
91+
return [
92+
['url'],
93+
['Url'],
94+
[Helper\Url::class],
95+
];
96+
}
97+
98+
/**
99+
* @group 71
100+
* @dataProvider urlHelperNames
101+
*/
102+
public function testUrlHelperFactoryCanBeInvokedViaShortNameOrFullClassName($name)
103+
{
104+
$routeMatch = $this->prophesize(RouteMatch::class)->reveal();
105+
$mvcEvent = $this->prophesize(MvcEvent::class);
106+
$mvcEvent->getRouteMatch()->willReturn($routeMatch);
107+
108+
$application = $this->prophesize(Application::class);
109+
$application->getMvcEvent()->willReturn($mvcEvent->reveal());
110+
111+
$router = $this->prophesize(RouteStackInterface::class)->reveal();
112+
113+
$this->services->setService('HttpRouter', $router);
114+
$this->services->setService('Router', $router);
115+
$this->services->setService('application', $application->reveal());
116+
$this->services->setService('config', []);
117+
118+
$manager = $this->factory->createService($this->services);
119+
$helper = $manager->get($name);
120+
121+
$this->assertAttributeSame($routeMatch, 'routeMatch', $helper, 'Route match was not injected');
122+
$this->assertAttributeSame($router, 'router', $helper, 'Router was not injected');
123+
}
124+
125+
public function basePathConfiguration()
126+
{
127+
$names = ['basepath', 'basePath', 'BasePath', Helper\BasePath::class];
128+
$configurations = [
129+
'console' => [[
130+
'config' => [
131+
'view_manager' => [
132+
'base_path_console' => '/foo/bar',
133+
],
134+
],
135+
], '/foo/bar'],
136+
137+
'hard-coded' => [[
138+
'config' => [
139+
'view_manager' => [
140+
'base_path' => '/foo/baz',
141+
],
142+
],
143+
], '/foo/baz'],
144+
145+
'request-base' => [[
146+
'config' => [], // fails creating plugin manager without this
147+
'request' => function () {
148+
$request = $this->prophesize(Request::class);
149+
$request->getBasePath()->willReturn('/foo/bat');
150+
return $request->reveal();
151+
},
152+
], '/foo/bat'],
153+
];
154+
155+
foreach ($names as $name) {
156+
foreach ($configurations as $testcase => $arguments) {
157+
array_unshift($arguments, $name);
158+
$testcase .= '-' . $name;
159+
yield $testcase => $arguments;
160+
}
161+
}
162+
}
163+
164+
/**
165+
* @group 71
166+
* @dataProvider basePathConfiguration
167+
*/
168+
public function testBasePathHelperFactoryCanBeInvokedViaShortNameOrFullClassName($name, array $services, $expected)
169+
{
170+
foreach ($services as $key => $value) {
171+
if (is_callable($value)) {
172+
$this->services->setFactory($key, $value);
173+
continue;
174+
}
175+
176+
$this->services->setService($key, $value);
177+
}
178+
179+
$plugins = $this->factory->createService($this->services);
180+
$helper = $plugins->get($name);
181+
$this->assertInstanceof(Helper\BasePath::class, $helper);
182+
$this->assertEquals($expected, $helper());
183+
}
184+
185+
public function doctypeHelperNames()
186+
{
187+
return [
188+
['doctype'],
189+
['Doctype'],
190+
[Helper\Doctype::class],
191+
];
192+
}
193+
194+
/**
195+
* @group 71
196+
* @dataProvider doctypeHelperNames
197+
*/
198+
public function testDoctypeHelperFactoryCanBeInvokedViaShortNameOrFullClassName($name)
199+
{
200+
$this->services->setService('config', [
201+
'view_manager' => [
202+
'doctype' => Helper\Doctype::HTML5,
203+
],
204+
]);
205+
206+
$plugins = $this->factory->createService($this->services);
207+
$helper = $plugins->get($name);
208+
$this->assertInstanceof(Helper\Doctype::class, $helper);
209+
$this->assertEquals('<!DOCTYPE html>', (string) $helper);
210+
}
84211
}

0 commit comments

Comments
 (0)