diff --git a/src/ServiceManager.php b/src/ServiceManager.php index 67d1a441..2d16e527 100644 --- a/src/ServiceManager.php +++ b/src/ServiceManager.php @@ -555,25 +555,7 @@ 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->createDelegatorFromFactory($cName, $rName); } return $this->doCreate($rName, $cName); @@ -582,22 +564,21 @@ public function create($name) /** * 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 + * @param DelegatorFactoryInterface|callable $delegatorFactory the delegator factory + * @param string $rName requested service name + * @param string $cName canonical service name + * @param callable $creationCallback callback for instantiating the real service * * @return callable */ - private function createDelegatorCallback($delegatorFactoryName, $rName, $cName, $creationCallback) + private function createDelegatorCallback($delegatorFactory, $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); + return function () use ($serviceManager, $delegatorFactory, $rName, $cName, $creationCallback) { + return $delegatorFactory instanceof DelegatorFactoryInterface + ? $delegatorFactory->createDelegatorWithName($serviceManager, $cName, $rName, $creationCallback) + : $delegatorFactory($serviceManager, $cName, $rName, $creationCallback); }; } @@ -1104,6 +1085,50 @@ protected function createFromAbstractFactory($canonicalName, $requestedName) return $instance; } + /** + * @param $canonicalName + * @param $requestedName + * @return mixed + * @throws Exception\ServiceNotCreatedException + */ + protected function createDelegatorFromFactory($canonicalName, $requestedName) + { + $serviceManager = $this; + $delegatorsCount = count($this->delegators[$canonicalName]); + $creationCallback = function () use ($serviceManager, $requestedName, $canonicalName) { + return $serviceManager->doCreate($requestedName, $canonicalName); + }; + + for ($i = 0; $i < $delegatorsCount; $i += 1) { + + $delegatorFactory = $this->delegators[$canonicalName][$i]; + + if (is_string($delegatorFactory)) { + $delegatorFactory = !$this->has($delegatorFactory) && class_exists($delegatorFactory, true) ? + new $delegatorFactory + : $this->get($delegatorFactory); + $this->delegators[$canonicalName][$i] = $delegatorFactory; + } + + if (!$delegatorFactory instanceof DelegatorFactoryInterface && !is_callable($delegatorFactory)) { + throw new Exception\ServiceNotCreatedException(sprintf( + 'While attempting to create %s%s an invalid factory was registered for this instance type.', + $canonicalName, + ($requestedName ? '(alias: ' . $requestedName . ')' : '') + )); + } + + $creationCallback = $this->createDelegatorCallback( + $delegatorFactory, + $requestedName, + $canonicalName, + $creationCallback + ); + } + + return $creationCallback($serviceManager, $canonicalName, $requestedName, $creationCallback); + } + /** * Checks if the object has this class as one of its parents * diff --git a/test/AbstractPluginManagerTest.php b/test/AbstractPluginManagerTest.php index 1d8f2b31..2cf31c4a 100644 --- a/test/AbstractPluginManagerTest.php +++ b/test/AbstractPluginManagerTest.php @@ -17,6 +17,7 @@ use ZendTest\ServiceManager\TestAsset\FooCounterAbstractFactory; use ZendTest\ServiceManager\TestAsset\FooPluginManager; +use ZendTest\ServiceManager\TestAsset\MockSelfReturningDelegatorFactory; class AbstractPluginManagerTest extends \PHPUnit_Framework_TestCase { @@ -117,4 +118,78 @@ public function testCallableObjectWithMutableCreationOptions() $method->setAccessible(true); $method->invoke($this->pluginManager, $callable, 'foo', 'bar'); } + + public function testValidatePluginIsCalledWithDelegatorFactoryIfItsAService() + { + $pluginManager = $this->getMockForAbstractClass('Zend\ServiceManager\AbstractPluginManager'); + $delegatorFactory = $this->getMock('Zend\\ServiceManager\\DelegatorFactoryInterface'); + + $pluginManager->setService('delegator-factory', $delegatorFactory); + $pluginManager->addDelegator('foo-service', 'delegator-factory'); + + $pluginManager->expects($this->once()) + ->method('validatePlugin') + ->with($delegatorFactory); + + $pluginManager->create('foo-service'); + } + + public function testSingleDelegatorUsage() + { + $delegatorFactory = $this->getMock('Zend\\ServiceManager\\DelegatorFactoryInterface'); + $pluginManager = $this->getMockForAbstractClass('Zend\ServiceManager\AbstractPluginManager'); + $realService = $this->getMock('stdClass', array(), array(), 'RealService'); + $delegator = $this->getMock('stdClass', array(), array(), 'Delegator'); + + $delegatorFactory + ->expects($this->once()) + ->method('createDelegatorWithName') + ->with( + $pluginManager, + 'fooservice', + 'foo-service', + $this->callback(function ($callback) use ($realService) { + if (!is_callable($callback)) { + return false; + } + + return call_user_func($callback) === $realService; + }) + ) + ->will($this->returnValue($delegator)); + + $pluginManager->setFactory('foo-service', function() use ($realService) { + return $realService; + }); + $pluginManager->addDelegator('foo-service', $delegatorFactory); + + $pluginManager->expects($this->once()) + ->method('validatePlugin') + ->with($delegator); + + $this->assertSame($delegator, $pluginManager->get('foo-service')); + } + + public function testMultipleDelegatorsUsage() + { + $pluginManager = $this->getMockForAbstractClass('Zend\ServiceManager\AbstractPluginManager'); + + $fooDelegator = new MockSelfReturningDelegatorFactory(); + $barDelegator = new MockSelfReturningDelegatorFactory(); + + $pluginManager->addDelegator('foo-service', $fooDelegator); + $pluginManager->addDelegator('foo-service', $barDelegator); + $pluginManager->setInvokableClass('foo-service', 'stdClass'); + + $pluginManager->expects($this->once()) + ->method('validatePlugin') + ->with($barDelegator); + + $this->assertSame($barDelegator, $pluginManager->get('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/test/ServiceManagerTest.php b/test/ServiceManagerTest.php index 0176de14..2812cdbb 100644 --- a/test/ServiceManagerTest.php +++ b/test/ServiceManagerTest.php @@ -753,6 +753,7 @@ public function testRetrieveServiceFromPeeringServiceManagerIfretrieveFromPeerin /** * @covers Zend\ServiceManager\ServiceManager::create + * @covers Zend\ServiceManager\ServiceManager::createDelegatorFromFactory * @covers Zend\ServiceManager\ServiceManager::createDelegatorCallback * @covers Zend\ServiceManager\ServiceManager::addDelegator */ @@ -783,13 +784,12 @@ public function testUsesDelegatorWhenAvailable() ) ->will($this->returnValue($delegator)); - //die(var_dump($this->serviceManager)); - $this->assertSame($delegator, $this->serviceManager->create('foo-service')); } /** * @covers Zend\ServiceManager\ServiceManager::create + * @covers Zend\ServiceManager\ServiceManager::createDelegatorFromFactory * @covers Zend\ServiceManager\ServiceManager::createDelegatorCallback * @covers Zend\ServiceManager\ServiceManager::addDelegator */ @@ -852,4 +852,106 @@ public function testResolveCircularAliasReferenceThrowsException() // This should throw the exception $this->serviceManager->get('baz-alias'); } + + /** + * @covers Zend\ServiceManager\ServiceManager::createDelegatorFromFactory + */ + public function testDelegatorFactoryWhenNotRegisteredAsService() + { + $delegator = $this->getMock('Zend\\ServiceManager\\DelegatorFactoryInterface'); + + $this->serviceManager->addDelegator('foo-service', $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::createDelegatorFromFactory + * @covers Zend\ServiceManager\ServiceManager::createDelegatorCallback + * @covers Zend\ServiceManager\ServiceManager::addDelegator + */ + public function testMultipleDelegatorFactoriesWhenNotRegisteredAsServices() + { + $fooDelegator = new MockSelfReturningDelegatorFactory(); + $barDelegator = new MockSelfReturningDelegatorFactory(); + + $this->serviceManager->addDelegator('foo-service', $fooDelegator); + $this->serviceManager->addDelegator('foo-service', $barDelegator); + $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)); + } + + public function testInvalidDelegatorFactoryThrowsException() + { + $delegatorFactory = new \stdClass; + $this->serviceManager->addDelegator('foo-service', $delegatorFactory); + + try { + $this->serviceManager->create('foo-service'); + $this->fail('Expected exception was not raised'); + }catch (Exception\ServiceNotCreatedException $expected) { + $this->assertRegExp('/invalid factory/', $expected->getMessage()); + return; + } + } + + public function testInvalidDelegatorFactoryAmongMultipleOnesThrowsException() + { + $this->serviceManager->addDelegator('foo-service', new MockSelfReturningDelegatorFactory()); + $this->serviceManager->addDelegator('foo-service', new MockSelfReturningDelegatorFactory()); + $this->serviceManager->addDelegator('foo-service', 'stdClass'); + + try { + $this->serviceManager->create('foo-service'); + $this->fail('Expected exception was not raised'); + }catch (Exception\ServiceNotCreatedException $expected) { + $this->assertRegExp('/invalid factory/', $expected->getMessage()); + return; + } + } + + public function testDelegatorFromCallback() + { + $realService = $this->getMock('stdClass', array(), array(), 'RealService'); + $delegator = $this->getMock('stdClass', array(), array(), 'Delegator'); + + $delegatorFactoryCallback = function($serviceManager, $cName, $rName, $callback) use ($delegator) { + $delegator->real = call_user_func($callback); + return $delegator; + }; + + $this->serviceManager->setFactory('foo-service', function() use ($realService) { return $realService; } ); + $this->serviceManager->addDelegator('foo-service', $delegatorFactoryCallback); + + $service = $this->serviceManager->create('foo-service'); + + $this->assertSame($delegator, $service); + $this->assertSame($realService, $service->real); + } }