diff --git a/library/Zend/ServiceManager/Config.php b/library/Zend/ServiceManager/Config.php index 94e1bdcc270..f0a3e3ee23b 100644 --- a/library/Zend/ServiceManager/Config.php +++ b/library/Zend/ServiceManager/Config.php @@ -106,6 +106,17 @@ public function getShared() return (isset($this->config['shared'])) ? $this->config['shared'] : array(); } + /** + * Get the delegator services map, with keys being the services acting as delegates, + * and values being the delegator factories names + * + * @return array + */ + public function getDelegators() + { + return (isset($this->config['delegators'])) ? $this->config['delegators'] : array(); + } + /** * Configure service manager * @@ -145,5 +156,11 @@ public function configureServiceManager(ServiceManager $serviceManager) foreach ($this->getShared() as $name => $isShared) { $serviceManager->setShared($name, $isShared); } + + foreach ($this->getDelegators() as $originalServiceName => $delegators) { + foreach ($delegators as $delegator) { + $serviceManager->addDelegator($originalServiceName, $delegator); + } + } } } diff --git a/library/Zend/ServiceManager/DelegatorFactoryInterface.php b/library/Zend/ServiceManager/DelegatorFactoryInterface.php new file mode 100644 index 00000000000..7d92e313043 --- /dev/null +++ b/library/Zend/ServiceManager/DelegatorFactoryInterface.php @@ -0,0 +1,28 @@ +delegators[$this->canonicalizeName($serviceName)])) { + $this->delegators[$this->canonicalizeName($serviceName)] = array(); + } + + $this->delegators[$this->canonicalizeName($serviceName)][] = $delegatorFactoryName; + + return $this; + } + /** * Add initializer * @@ -475,17 +499,14 @@ public function get($name, $usePeeringServiceManagers = true) } /** - * Create an instance + * Create an instance of the requested service * * @param string|array $name + * * @return bool|object - * @throws Exception\ServiceNotFoundException - * @throws Exception\ServiceNotCreatedException */ public function create($name) { - $instance = false; - if (is_array($name)) { list($cName, $rName) = $name; } else { @@ -499,6 +520,67 @@ public function create($name) } } + if (isset($this->delegators[$cName])) { + $serviceManager = $this; + $additionalDelegators = count($this->delegators[$cName]) - 1; + $creationCallback = function () use ($serviceManager, $rName, $cName) { + return $serviceManager->doCreate($rName, $cName); + }; + + for ($i = 0; $i < $additionalDelegators; $i += 1) { + $creationCallback = $this->createDelegatorCallback( + $this->delegators[$cName][$i], + $rName, + $cName, + $creationCallback + ); + } + + /* @var $delegatorFactory DelegatorFactoryInterface */ + $delegatorFactory = $this->get($this->delegators[$cName][$i]); + + return $delegatorFactory->createDelegatorWithName($this, $cName, $rName, $creationCallback); + } + + return $this->doCreate($rName, $cName); + } + + /** + * Creates a callback that uses a delegator to create a service + * + * @param string $delegatorFactoryName name of the delegator factory service + * @param string $rName requested service name + * @param string $cName canonical service name + * @param callable $creationCallback callback that is responsible for instantiating the service + * + * @return callable + */ + private function createDelegatorCallback($delegatorFactoryName, $rName, $cName, $creationCallback) + { + $serviceManager = $this; + + return function () use ($serviceManager, $delegatorFactoryName, $rName, $cName, $creationCallback) { + /* @var $delegatorFactory DelegatorFactoryInterface */ + $delegatorFactory = $serviceManager->get($delegatorFactoryName); + + return $delegatorFactory->createDelegatorWithName($serviceManager, $cName, $rName, $creationCallback); + }; + } + + /** + * Actually creates the service + * + * @param string $rName real service name + * @param string $cName canonicalized service name + * + * @return bool|mixed|null|object + * @throws Exception\ServiceNotFoundException + * + * @internal this method is internal because of PHP 5.3 compatibility - do not explicitly use it + */ + public function doCreate($rName, $cName) + { + $instance = false; if (isset($this->factories[$cName])) { $instance = $this->createFromFactory($cName, $rName); diff --git a/tests/ZendTest/ServiceManager/ServiceManagerTest.php b/tests/ZendTest/ServiceManager/ServiceManagerTest.php index 611e3d92925..389657cf051 100644 --- a/tests/ZendTest/ServiceManager/ServiceManagerTest.php +++ b/tests/ZendTest/ServiceManager/ServiceManagerTest.php @@ -18,6 +18,7 @@ use Zend\ServiceManager\Config; use ZendTest\ServiceManager\TestAsset\FooCounterAbstractFactory; +use ZendTest\ServiceManager\TestAsset\MockSelfReturningDelegatorFactory; class ServiceManagerTest extends \PHPUnit_Framework_TestCase { @@ -728,4 +729,62 @@ public function testRetrieveServiceFromPeeringServiceManagerIfretrieveFromPeerin $this->assertEquals($serviceManagerChild->get($foo1), $boo2); $this->assertEquals($this->serviceManager->get($foo1), $boo2); } + + /** + * @covers Zend\ServiceManager\ServiceManager::create + * @covers Zend\ServiceManager\ServiceManager::createDelegatorCallback + * @covers Zend\ServiceManager\ServiceManager::addDelegator + */ + public function testUsesDelegatorWhenAvailable() + { + $delegator = $this->getMock('Zend\\ServiceManager\\DelegatorFactoryInterface'); + + $this->serviceManager->setService('foo-delegator', $delegator); + $this->serviceManager->addDelegator('foo-service', 'foo-delegator'); + $this->serviceManager->setInvokableClass('foo-service', 'stdClass'); + + $delegator + ->expects($this->once()) + ->method('createDelegatorWithName') + ->with( + $this->serviceManager, + 'fooservice', + 'foo-service', + $this->callback(function ($callback) { + if (!is_callable($callback)) { + return false; + } + + $service = call_user_func($callback); + + return $service instanceof \stdClass; + }) + ) + ->will($this->returnValue($delegator)); + + $this->assertSame($delegator, $this->serviceManager->create('foo-service')); + } + + /** + * @covers Zend\ServiceManager\ServiceManager::create + * @covers Zend\ServiceManager\ServiceManager::createDelegatorCallback + * @covers Zend\ServiceManager\ServiceManager::addDelegator + */ + public function testUsesMultipleDelegates() + { + $fooDelegator = new MockSelfReturningDelegatorFactory(); + $barDelegator = new MockSelfReturningDelegatorFactory(); + + $this->serviceManager->setService('foo-delegate', $fooDelegator); + $this->serviceManager->setService('bar-delegate', $barDelegator); + $this->serviceManager->addDelegator('foo-service', 'foo-delegate'); + $this->serviceManager->addDelegator('foo-service', 'bar-delegate'); + $this->serviceManager->setInvokableClass('foo-service', 'stdClass'); + + $this->assertSame($barDelegator, $this->serviceManager->create('foo-service')); + $this->assertCount(1, $barDelegator->instances); + $this->assertCount(1, $fooDelegator->instances); + $this->assertInstanceOf('stdClass', array_shift($fooDelegator->instances)); + $this->assertSame($fooDelegator, array_shift($barDelegator->instances)); + } } diff --git a/tests/ZendTest/ServiceManager/TestAsset/MockSelfReturningDelegatorFactory.php b/tests/ZendTest/ServiceManager/TestAsset/MockSelfReturningDelegatorFactory.php new file mode 100644 index 00000000000..2bf70697a31 --- /dev/null +++ b/tests/ZendTest/ServiceManager/TestAsset/MockSelfReturningDelegatorFactory.php @@ -0,0 +1,35 @@ +instances[] = call_user_func($callback); + + return $this; + } +}