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

Commit

Permalink
Merge branch 'weierophinney-hotfix/forwards-compat'
Browse files Browse the repository at this point in the history
Close #60
  • Loading branch information
akrabat committed Jan 8, 2016
2 parents 3b55b73 + 4622c99 commit 2a684f2
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file, in reverse

### Added

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

### Deprecated

Expand Down
52 changes: 49 additions & 3 deletions src/AbstractPluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Zend\ServiceManager;

use Interop\Container\ContainerInterface;
use Exception as BaseException;

/**
Expand Down Expand Up @@ -57,11 +58,56 @@ abstract class AbstractPluginManager extends ServiceManager implements ServiceLo
* Add a default initializer to ensure the plugin is valid after instance
* creation.
*
* @param null|ConfigInterface $configuration
* Additionally, the constructor provides forwards compatibility with v3 by
* overloading the initial argument. v2 usage expects either null or a
* ConfigInterface instance, and will ignore any other arguments. v3 expects
* a ContainerInterface instance, and will use an array of configuration to
* seed the current instance with services. In most cases, you can ignore the
* constructor unless you are writing a specialized factory for your plugin
* manager or overriding it.
*
* @param null|ConfigInterface|ContainerInterface $configOrContainerInstance
* @param array $v3config If $configOrContainerInstance is a container, this
* value will be passed to the parent constructor.
* @throws Exception\InvalidArgumentException if $configOrContainerInstance
* is neither null, nor a ConfigInterface, nor a ContainerInterface.
*/
public function __construct(ConfigInterface $configuration = null)
public function __construct($configOrContainerInstance = null, array $v3config = [])
{
parent::__construct($configuration);
if (null !== $configOrContainerInstance
&& ! $configOrContainerInstance instanceof ConfigInterface
&& ! $configOrContainerInstance instanceof ContainerInterface
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects a ConfigInterface instance or ContainerInterface instance; received %s',
get_class($this),
(is_object($configOrContainerInstance)
? get_class($configOrContainerInstance)
: gettype($configOrContainerInstance)
)
));
}

if ($configOrContainerInstance instanceof ContainerInterface) {
if (property_exists($this, 'serviceLocator')) {
if (! empty($v3config)) {
parent::__construct(new Config($v3config));
}
$this->serviceLocator = $configOrContainerInstance;
}

if (property_exists($this, 'creationContext')) {
if (! empty($v3config)) {
parent::__construct($v3config);
}
$this->creationContext = $configOrContainerInstance;
}
}

if ($configOrContainerInstance instanceof ConfigInterface) {
parent::__construct($configOrContainerInstance);
}

$this->addInitializer(function ($instance) {
if ($instance instanceof ServiceLocatorAwareInterface) {
$instance->setServiceLocator($this);
Expand Down
67 changes: 67 additions & 0 deletions src/Factory/InvokableFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace Zend\ServiceManager\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Exception\InvalidServiceNameException;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

/**
* Factory for instantiating classes with no dependencies or which accept a single array.
*
* The InvokableFactory can be used for any class that:
*
* - has no constructor arguments;
* - accepts a single array of arguments via the constructor.
*
* It replaces the "invokables" and "invokable class" functionality of the v2
* service manager, and can also be used in v2 code for forwards compatibility
* with v3.
*/
final class InvokableFactory implements FactoryInterface
{
/**
* Create an instance of the requested class name.
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
* @return object
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return (null === $options) ? new $requestedName : new $requestedName($options);
}

/**
* Create an instance of the named service.
*
* If `$requestedName` is not provided, raises an exception; otherwise,
* proxies to the `__invoke()` method to create an instance of the
* requested class.
*
* @param ServiceLocatorInterface $serviceLocator
* @param null|string $canonicalName Ignored
* @param null|string $requestedName
* @return object
* @throws InvalidServiceNameException
*/
public function createService(ServiceLocatorInterface $serviceLocator, $canonicalName = null, $requestedName = null)
{
if (! $requestedName) {
throw new InvalidServiceNameException(sprintf(
'%s requires that the requested name is provided on invocation; please update your tests or consuming container',
__CLASS__
));
}
return $this($serviceLocator, $requestedName);
}
}
50 changes: 50 additions & 0 deletions test/AbstractPluginManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

namespace ZendTest\ServiceManager;

use Interop\Container\ContainerInterface;
use ReflectionClass;
use ReflectionObject;
use Zend\ServiceManager\Config;
use Zend\ServiceManager\Exception\InvalidArgumentException;
use Zend\ServiceManager\Exception\RuntimeException;
use Zend\ServiceManager\ServiceManager;
use ZendTest\ServiceManager\TestAsset\FooPluginManager;
Expand Down Expand Up @@ -283,4 +285,52 @@ public function testWillResetAutoInvokableServiceIfNotValid()
$this->assertTrue($pluginManager->has(__CLASS__));
}
}

/**
* @group migration
*/
public function testConstructorAllowsPassingContainerAsFirstArgument()
{
$container = $this->prophesize(ContainerInterface::class);
$pluginManager = new FooPluginManager($container->reveal());
$this->assertSame($container->reveal(), $pluginManager->getServiceLocator());
}

/**
* @group migration
*/
public function testConstructorAllowsPassingContainerAndConfigurationArrayAsArguments()
{
$container = $this->prophesize(ContainerInterface::class);
$pluginManager = new FooPluginManager($container->reveal(), ['services' => [
__CLASS__ => $this,
]]);
$this->assertSame($container->reveal(), $pluginManager->getServiceLocator());
$this->assertTrue($pluginManager->has(__CLASS__));
}

public function invalidConstructorArguments()
{
return [
'true' => [true],
'false' => [false],
'zero' => [0],
'int' => [1],
'zero-float' => [0.0],
'float' => [1.1],
'string' => ['invalid'],
'array' => [['services' => [__CLASS__ => $this]]],
'object' => [(object) ['services' => [__CLASS__ => $this]]],
];
}

/**
* @group migration
* @dataProvider invalidConstructorArguments
*/
public function testPassingArgumentsOtherThanNullConfigOrContainerAsFirstConstructorArgRaisesException($arg)
{
$this->setExpectedException(InvalidArgumentException::class);
new FooPluginManager($arg);
}
}
32 changes: 32 additions & 0 deletions test/Factory/InvokableFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace ZendTest\ServiceManager\Factory;

use Interop\Container\ContainerInterface;
use PHPUnit_Framework_TestCase as TestCase;
use Zend\ServiceManager\Factory\InvokableFactory;
use ZendTest\ServiceManager\TestAsset\InvokableObject;

/**
* @covers \Zend\ServiceManager\Factory\InvokableFactory
*/
class InvokableFactoryTest extends TestCase
{
public function testCanCreateObject()
{
$container = $this->getMock(ContainerInterface::class);
$factory = new InvokableFactory();

$object = $factory($container, InvokableObject::class, ['foo' => 'bar']);

$this->assertInstanceOf(InvokableObject::class, $object);
$this->assertEquals(['foo' => 'bar'], $object->options);
}
}
34 changes: 34 additions & 0 deletions test/TestAsset/InvokableObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace ZendTest\ServiceManager\TestAsset;

class InvokableObject
{
/**
* @var array
*/
public $options;

/**
* @param array $options
*/
public function __construct(array $options = [])
{
$this->options = $options;
}

/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
}

0 comments on commit 2a684f2

Please sign in to comment.