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

Commit 2a684f2

Browse files
committed
Merge branch 'weierophinney-hotfix/forwards-compat'
Close #60
2 parents 3b55b73 + 4622c99 commit 2a684f2

File tree

6 files changed

+234
-4
lines changed

6 files changed

+234
-4
lines changed

CHANGELOG.md

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

77
### Added
88

9-
- Nothing.
9+
- [#60](https://github.com/zendframework/zend-servicemanager/pull/60) adds forward compatibility features for
10+
`AbstractPluingManager` and introduces `InvokableFactory` to help forward migration to version 3.
1011

1112
### Deprecated
1213

src/AbstractPluginManager.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace Zend\ServiceManager;
1111

12+
use Interop\Container\ContainerInterface;
1213
use Exception as BaseException;
1314

1415
/**
@@ -57,11 +58,56 @@ abstract class AbstractPluginManager extends ServiceManager implements ServiceLo
5758
* Add a default initializer to ensure the plugin is valid after instance
5859
* creation.
5960
*
60-
* @param null|ConfigInterface $configuration
61+
* Additionally, the constructor provides forwards compatibility with v3 by
62+
* overloading the initial argument. v2 usage expects either null or a
63+
* ConfigInterface instance, and will ignore any other arguments. v3 expects
64+
* a ContainerInterface instance, and will use an array of configuration to
65+
* seed the current instance with services. In most cases, you can ignore the
66+
* constructor unless you are writing a specialized factory for your plugin
67+
* manager or overriding it.
68+
*
69+
* @param null|ConfigInterface|ContainerInterface $configOrContainerInstance
70+
* @param array $v3config If $configOrContainerInstance is a container, this
71+
* value will be passed to the parent constructor.
72+
* @throws Exception\InvalidArgumentException if $configOrContainerInstance
73+
* is neither null, nor a ConfigInterface, nor a ContainerInterface.
6174
*/
62-
public function __construct(ConfigInterface $configuration = null)
75+
public function __construct($configOrContainerInstance = null, array $v3config = [])
6376
{
64-
parent::__construct($configuration);
77+
if (null !== $configOrContainerInstance
78+
&& ! $configOrContainerInstance instanceof ConfigInterface
79+
&& ! $configOrContainerInstance instanceof ContainerInterface
80+
) {
81+
throw new Exception\InvalidArgumentException(sprintf(
82+
'%s expects a ConfigInterface instance or ContainerInterface instance; received %s',
83+
get_class($this),
84+
(is_object($configOrContainerInstance)
85+
? get_class($configOrContainerInstance)
86+
: gettype($configOrContainerInstance)
87+
)
88+
));
89+
}
90+
91+
if ($configOrContainerInstance instanceof ContainerInterface) {
92+
if (property_exists($this, 'serviceLocator')) {
93+
if (! empty($v3config)) {
94+
parent::__construct(new Config($v3config));
95+
}
96+
$this->serviceLocator = $configOrContainerInstance;
97+
}
98+
99+
if (property_exists($this, 'creationContext')) {
100+
if (! empty($v3config)) {
101+
parent::__construct($v3config);
102+
}
103+
$this->creationContext = $configOrContainerInstance;
104+
}
105+
}
106+
107+
if ($configOrContainerInstance instanceof ConfigInterface) {
108+
parent::__construct($configOrContainerInstance);
109+
}
110+
65111
$this->addInitializer(function ($instance) {
66112
if ($instance instanceof ServiceLocatorAwareInterface) {
67113
$instance->setServiceLocator($this);

src/Factory/InvokableFactory.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace Zend\ServiceManager\Factory;
11+
12+
use Interop\Container\ContainerInterface;
13+
use Zend\ServiceManager\Exception\InvalidServiceNameException;
14+
use Zend\ServiceManager\FactoryInterface;
15+
use Zend\ServiceManager\ServiceLocatorInterface;
16+
17+
/**
18+
* Factory for instantiating classes with no dependencies or which accept a single array.
19+
*
20+
* The InvokableFactory can be used for any class that:
21+
*
22+
* - has no constructor arguments;
23+
* - accepts a single array of arguments via the constructor.
24+
*
25+
* It replaces the "invokables" and "invokable class" functionality of the v2
26+
* service manager, and can also be used in v2 code for forwards compatibility
27+
* with v3.
28+
*/
29+
final class InvokableFactory implements FactoryInterface
30+
{
31+
/**
32+
* Create an instance of the requested class name.
33+
*
34+
* @param ContainerInterface $container
35+
* @param string $requestedName
36+
* @param null|array $options
37+
* @return object
38+
*/
39+
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
40+
{
41+
return (null === $options) ? new $requestedName : new $requestedName($options);
42+
}
43+
44+
/**
45+
* Create an instance of the named service.
46+
*
47+
* If `$requestedName` is not provided, raises an exception; otherwise,
48+
* proxies to the `__invoke()` method to create an instance of the
49+
* requested class.
50+
*
51+
* @param ServiceLocatorInterface $serviceLocator
52+
* @param null|string $canonicalName Ignored
53+
* @param null|string $requestedName
54+
* @return object
55+
* @throws InvalidServiceNameException
56+
*/
57+
public function createService(ServiceLocatorInterface $serviceLocator, $canonicalName = null, $requestedName = null)
58+
{
59+
if (! $requestedName) {
60+
throw new InvalidServiceNameException(sprintf(
61+
'%s requires that the requested name is provided on invocation; please update your tests or consuming container',
62+
__CLASS__
63+
));
64+
}
65+
return $this($serviceLocator, $requestedName);
66+
}
67+
}

test/AbstractPluginManagerTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
namespace ZendTest\ServiceManager;
1111

12+
use Interop\Container\ContainerInterface;
1213
use ReflectionClass;
1314
use ReflectionObject;
1415
use Zend\ServiceManager\Config;
16+
use Zend\ServiceManager\Exception\InvalidArgumentException;
1517
use Zend\ServiceManager\Exception\RuntimeException;
1618
use Zend\ServiceManager\ServiceManager;
1719
use ZendTest\ServiceManager\TestAsset\FooPluginManager;
@@ -283,4 +285,52 @@ public function testWillResetAutoInvokableServiceIfNotValid()
283285
$this->assertTrue($pluginManager->has(__CLASS__));
284286
}
285287
}
288+
289+
/**
290+
* @group migration
291+
*/
292+
public function testConstructorAllowsPassingContainerAsFirstArgument()
293+
{
294+
$container = $this->prophesize(ContainerInterface::class);
295+
$pluginManager = new FooPluginManager($container->reveal());
296+
$this->assertSame($container->reveal(), $pluginManager->getServiceLocator());
297+
}
298+
299+
/**
300+
* @group migration
301+
*/
302+
public function testConstructorAllowsPassingContainerAndConfigurationArrayAsArguments()
303+
{
304+
$container = $this->prophesize(ContainerInterface::class);
305+
$pluginManager = new FooPluginManager($container->reveal(), ['services' => [
306+
__CLASS__ => $this,
307+
]]);
308+
$this->assertSame($container->reveal(), $pluginManager->getServiceLocator());
309+
$this->assertTrue($pluginManager->has(__CLASS__));
310+
}
311+
312+
public function invalidConstructorArguments()
313+
{
314+
return [
315+
'true' => [true],
316+
'false' => [false],
317+
'zero' => [0],
318+
'int' => [1],
319+
'zero-float' => [0.0],
320+
'float' => [1.1],
321+
'string' => ['invalid'],
322+
'array' => [['services' => [__CLASS__ => $this]]],
323+
'object' => [(object) ['services' => [__CLASS__ => $this]]],
324+
];
325+
}
326+
327+
/**
328+
* @group migration
329+
* @dataProvider invalidConstructorArguments
330+
*/
331+
public function testPassingArgumentsOtherThanNullConfigOrContainerAsFirstConstructorArgRaisesException($arg)
332+
{
333+
$this->setExpectedException(InvalidArgumentException::class);
334+
new FooPluginManager($arg);
335+
}
286336
}

test/Factory/InvokableFactoryTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace ZendTest\ServiceManager\Factory;
11+
12+
use Interop\Container\ContainerInterface;
13+
use PHPUnit_Framework_TestCase as TestCase;
14+
use Zend\ServiceManager\Factory\InvokableFactory;
15+
use ZendTest\ServiceManager\TestAsset\InvokableObject;
16+
17+
/**
18+
* @covers \Zend\ServiceManager\Factory\InvokableFactory
19+
*/
20+
class InvokableFactoryTest extends TestCase
21+
{
22+
public function testCanCreateObject()
23+
{
24+
$container = $this->getMock(ContainerInterface::class);
25+
$factory = new InvokableFactory();
26+
27+
$object = $factory($container, InvokableObject::class, ['foo' => 'bar']);
28+
29+
$this->assertInstanceOf(InvokableObject::class, $object);
30+
$this->assertEquals(['foo' => 'bar'], $object->options);
31+
}
32+
}

test/TestAsset/InvokableObject.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace ZendTest\ServiceManager\TestAsset;
11+
12+
class InvokableObject
13+
{
14+
/**
15+
* @var array
16+
*/
17+
public $options;
18+
19+
/**
20+
* @param array $options
21+
*/
22+
public function __construct(array $options = [])
23+
{
24+
$this->options = $options;
25+
}
26+
27+
/**
28+
* @return array
29+
*/
30+
public function getOptions()
31+
{
32+
return $this->options;
33+
}
34+
}

0 commit comments

Comments
 (0)