diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 0118e23d..5bc33f12 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -23,8 +23,8 @@ class InvalidArgumentException extends SplInvalidArgumentException implements Ex public static function fromInvalidInitializer($initializer) { return new self(sprintf( - 'An invalid initializer was registered. Expected a callable or an' - . ' instance of "%s"; received "%s"', + 'An invalid initializer was registered. Expected a valid function name or ' + . 'class name or a callable or an instance of "%s"; received "%s"', InitializerInterface::class, is_object($initializer) ? get_class($initializer) : gettype($initializer) )); diff --git a/src/ServiceManager.php b/src/ServiceManager.php index 73819938..216a68d8 100644 --- a/src/ServiceManager.php +++ b/src/ServiceManager.php @@ -33,6 +33,7 @@ use function spl_object_hash; use function sprintf; use function trigger_error; +use Zend\ServiceManager\Factory\InvokableFactory; /** * Service Manager. @@ -89,6 +90,13 @@ class ServiceManager implements ServiceLocatorInterface */ protected $factories = []; + /** + * A list of invokable classes + * + * @var string[]|callable[] + */ + protected $invokables = []; + /** * @var Initializer\InitializerInterface[]|callable[] */ @@ -171,9 +179,6 @@ public function __construct(array $config = []) $this->resolveAbstractFactories(null); } - if (! empty($this->invokables)) { - $this->createAliasesAndFactoriesForInvokables($this->invokables); - } $this->configure($config); } @@ -268,7 +273,7 @@ public function has($name) { $resolvedName = $this->aliases[$name] ?? $name; // Check services and factories first to speedup the most common requests. - if (isset($this->services[$resolvedName]) || isset($this->factories[$resolvedName])) { + if (isset($this->services[$resolvedName]) || isset($this->factories[$resolvedName]) || isset($this->invokables[$resolvedName])) { return true; } @@ -352,7 +357,7 @@ public function configure(array $config) } if (! empty($config['invokables'])) { - $this->createAliasesAndFactoriesForInvokables($config['invokables']); + $this->invokables = $config['invokables'] + $this->invokables; } if (! empty($config['factories'])) { @@ -425,7 +430,7 @@ public function setInvokableClass($name, $class = null) if (isset($this->services[$name]) && ! $this->allowOverride) { throw ContainerModificationsNotAllowedException::fromExistingService($name); } - $this->createAliasesAndFactoriesForInvokables([$name => $class ?? $name]); + $this->invokables[$name] = $class ?? $name; } /** @@ -564,7 +569,7 @@ private function resolveInitializers(array $initializers = null) * @return callable * @throws ServiceNotFoundException */ - private function getFactory($name) + private function getFactory(&$name) { $factory = $this->factories[$name] ?? null; @@ -580,6 +585,9 @@ private function getFactory($name) } return $factory; + } elseif (isset($this->invokables[$name])) { + $name = $this->invokables[$name]; + return new InvokableFactory(); } // Check abstract factories diff --git a/test/CommonServiceLocatorBehaviorsTrait.php b/test/CommonServiceLocatorBehaviorsTrait.php index 91c626fa..c63bcd81 100644 --- a/test/CommonServiceLocatorBehaviorsTrait.php +++ b/test/CommonServiceLocatorBehaviorsTrait.php @@ -21,17 +21,11 @@ use Zend\ServiceManager\ServiceLocatorInterface; use ZendTest\ServiceManager\TestAsset\CallTimesAbstractFactory; use ZendTest\ServiceManager\TestAsset\FailingAbstractFactory; -use ZendTest\ServiceManager\TestAsset\FailingExceptionWithStringAsCodeFactory; use ZendTest\ServiceManager\TestAsset\FailingFactory; +use ZendTest\ServiceManager\TestAsset\FailingExceptionWithStringAsCodeFactory; use ZendTest\ServiceManager\TestAsset\InvokableObject; use ZendTest\ServiceManager\TestAsset\SimpleAbstractFactory; -use function call_user_func_array; -use function restore_error_handler; -use function set_error_handler; -use ZendTest\ServiceManager\TestAsset\PreconfiguredServiceManager; -use ZendTest\ServiceManager\TestAsset\Foo; - trait CommonServiceLocatorBehaviorsTrait { /** @@ -52,7 +46,7 @@ public function testIsSharedByDefault() $object1 = $serviceManager->get(stdClass::class); $object2 = $serviceManager->get(stdClass::class); - self::assertSame($object1, $object2); + $this->assertSame($object1, $object2); } public function testCanDisableSharedByDefault() @@ -67,7 +61,7 @@ public function testCanDisableSharedByDefault() $object1 = $serviceManager->get(stdClass::class); $object2 = $serviceManager->get(stdClass::class); - self::assertNotSame($object1, $object2); + $this->assertNotSame($object1, $object2); } public function testCanDisableSharedForSingleService() @@ -84,7 +78,7 @@ public function testCanDisableSharedForSingleService() $object1 = $serviceManager->get(stdClass::class); $object2 = $serviceManager->get(stdClass::class); - self::assertNotSame($object1, $object2); + $this->assertNotSame($object1, $object2); } public function testCanEnableSharedForSingleService() @@ -102,7 +96,7 @@ public function testCanEnableSharedForSingleService() $object1 = $serviceManager->get(stdClass::class); $object2 = $serviceManager->get(stdClass::class); - self::assertSame($object1, $object2); + $this->assertSame($object1, $object2); } public function testCanBuildObjectWithInvokableFactory() @@ -115,8 +109,8 @@ public function testCanBuildObjectWithInvokableFactory() $object = $serviceManager->build(InvokableObject::class, ['foo' => 'bar']); - self::assertInstanceOf(InvokableObject::class, $object); - self::assertEquals(['foo' => 'bar'], $object->options); + $this->assertInstanceOf(InvokableObject::class, $object); + $this->assertEquals(['foo' => 'bar'], $object->options); } public function testCanCreateObjectWithClosureFactory() @@ -124,14 +118,14 @@ public function testCanCreateObjectWithClosureFactory() $serviceManager = $this->createContainer([ 'factories' => [ stdClass::class => function (ServiceLocatorInterface $serviceLocator, $className) { - self::assertEquals(stdClass::class, $className); + $this->assertEquals(stdClass::class, $className); return new stdClass(); } ] ]); $object = $serviceManager->get(stdClass::class); - self::assertInstanceOf(stdClass::class, $object); + $this->assertInstanceOf(stdClass::class, $object); } public function testCanCreateServiceWithAbstractFactory() @@ -142,7 +136,7 @@ public function testCanCreateServiceWithAbstractFactory() ] ]); - self::assertInstanceOf(DateTime::class, $serviceManager->get(DateTime::class)); + $this->assertInstanceOf(DateTime::class, $serviceManager->get(DateTime::class)); } public function testAllowsMultipleInstancesOfTheSameAbstractFactory() @@ -162,7 +156,7 @@ public function testAllowsMultipleInstancesOfTheSameAbstractFactory() $serviceManager->addAbstractFactory($obj2); $serviceManager->has(stdClass::class); - self::assertEquals(2, CallTimesAbstractFactory::getCallTimes()); + $this->assertEquals(2, CallTimesAbstractFactory::getCallTimes()); } public function testWillReUseAnExistingNamedAbstractFactoryInstance() @@ -178,7 +172,7 @@ public function testWillReUseAnExistingNamedAbstractFactoryInstance() $serviceManager->addAbstractFactory(CallTimesAbstractFactory::class); $serviceManager->has(stdClass::class); - self::assertEquals(1, CallTimesAbstractFactory::getCallTimes()); + $this->assertEquals(1, CallTimesAbstractFactory::getCallTimes()); } public function testCanCreateServiceWithAlias() @@ -195,9 +189,9 @@ public function testCanCreateServiceWithAlias() $object = $serviceManager->get('bar'); - self::assertInstanceOf(InvokableObject::class, $object); - self::assertTrue($serviceManager->has('bar')); - self::assertFalse($serviceManager->has('baz')); + $this->assertInstanceOf(InvokableObject::class, $object); + $this->assertTrue($serviceManager->has('bar')); + $this->assertFalse($serviceManager->has('baz')); } public function testCheckingServiceExistenceWithChecksAgainstAbstractFactories() @@ -211,8 +205,8 @@ public function testCheckingServiceExistenceWithChecksAgainstAbstractFactories() ] ]); - self::assertTrue($serviceManager->has(stdClass::class)); - self::assertTrue($serviceManager->has(DateTime::class)); + $this->assertTrue($serviceManager->has(stdClass::class)); + $this->assertTrue($serviceManager->has(DateTime::class)); } public function testBuildNeverSharesInstances() @@ -229,7 +223,7 @@ public function testBuildNeverSharesInstances() $object1 = $serviceManager->build(stdClass::class); $object2 = $serviceManager->build(stdClass::class, ['foo' => 'bar']); - self::assertNotSame($object1, $object2); + $this->assertNotSame($object1, $object2); } public function testInitializersAreRunAfterCreation() @@ -290,8 +284,8 @@ public function testConfigureCanAddNewServices() ] ]); - self::assertTrue($serviceManager->has(DateTime::class)); - self::assertFalse($serviceManager->has(stdClass::class)); + $this->assertTrue($serviceManager->has(DateTime::class)); + $this->assertFalse($serviceManager->has(stdClass::class)); $newServiceManager = $serviceManager->configure([ 'factories' => [ @@ -299,10 +293,10 @@ public function testConfigureCanAddNewServices() ] ]); - self::assertSame($serviceManager, $newServiceManager); + $this->assertSame($serviceManager, $newServiceManager); - self::assertTrue($newServiceManager->has(DateTime::class)); - self::assertTrue($newServiceManager->has(stdClass::class)); + $this->assertTrue($newServiceManager->has(DateTime::class)); + $this->assertTrue($newServiceManager->has(stdClass::class)); } public function testConfigureCanOverridePreviousSettings() @@ -324,7 +318,7 @@ public function testConfigureCanOverridePreviousSettings() ] ]); - self::assertSame($serviceManager, $newServiceManager); + $this->assertSame($serviceManager, $newServiceManager); $firstFactory->expects($this->never())->method('__invoke'); $secondFactory->expects($this->once())->method('__invoke'); @@ -342,7 +336,7 @@ public function testHasReturnsFalseIfServiceNotConfigured() stdClass::class => InvokableFactory::class, ], ]); - self::assertFalse($serviceManager->has('Some\Made\Up\Entry')); + $this->assertFalse($serviceManager->has('Some\Made\Up\Entry')); } /** @@ -355,7 +349,7 @@ public function testHasReturnsTrueIfServiceIsConfigured() stdClass::class => new stdClass, ], ]); - self::assertTrue($serviceManager->has(stdClass::class)); + $this->assertTrue($serviceManager->has(stdClass::class)); } /** @@ -368,7 +362,7 @@ public function testHasReturnsTrueIfFactoryIsConfigured() stdClass::class => InvokableFactory::class, ], ]); - self::assertTrue($serviceManager->has(stdClass::class)); + $this->assertTrue($serviceManager->has(stdClass::class)); } public function abstractFactories() @@ -391,7 +385,7 @@ public function testHasChecksAgainstAbstractFactories($abstractFactory, $expecte ], ]); - self::assertSame($expected, $serviceManager->has(DateTime::class)); + $this->assertSame($expected, $serviceManager->has(DateTime::class)); } /** @@ -437,40 +431,40 @@ function ($container, $instance) { ]); $dateTime = $serviceManager->get(DateTime::class); - self::assertInstanceOf(DateTime::class, $dateTime, 'DateTime service did not resolve as expected'); + $this->assertInstanceOf(DateTime::class, $dateTime, 'DateTime service did not resolve as expected'); $notShared = $serviceManager->get(DateTime::class); - self::assertInstanceOf(DateTime::class, $notShared, 'DateTime service did not re-resolve as expected'); - self::assertNotSame( + $this->assertInstanceOf(DateTime::class, $notShared, 'DateTime service did not re-resolve as expected'); + $this->assertNotSame( $dateTime, $notShared, 'Expected unshared instances for DateTime service but received shared instances' ); $config = $serviceManager->get('config'); - self::assertInternalType('array', $config, 'Config service did not resolve as expected'); - self::assertSame( + $this->assertInternalType('array', $config, 'Config service did not resolve as expected'); + $this->assertSame( $config, $serviceManager->get('config'), 'Config service resolved as unshared instead of shared' ); $stdClass = $serviceManager->get(stdClass::class); - self::assertInstanceOf(stdClass::class, $stdClass, 'stdClass service did not resolve as expected'); - self::assertSame( + $this->assertInstanceOf(stdClass::class, $stdClass, 'stdClass service did not resolve as expected'); + $this->assertSame( $stdClass, $serviceManager->get(stdClass::class), 'stdClass service should be shared, but resolved as unshared' ); - self::assertTrue( + $this->assertTrue( isset($stdClass->foo), 'Expected delegator to inject "foo" property in stdClass service, but it was not' ); - self::assertEquals('bar', $stdClass->foo, 'stdClass "foo" property was not injected correctly'); - self::assertTrue( + $this->assertEquals('bar', $stdClass->foo, 'stdClass "foo" property was not injected correctly'); + $this->assertTrue( isset($stdClass->bar), 'Expected initializer to inject "bar" property in stdClass service, but it was not' ); - self::assertEquals('baz', $stdClass->bar, 'stdClass "bar" property was not injected correctly'); + $this->assertEquals('baz', $stdClass->bar, 'stdClass "bar" property was not injected correctly'); } /** @@ -485,7 +479,7 @@ public function testCanSpecifyAbstractFactoryUsingStringViaConfiguration() ]); $dateTime = $serviceManager->get(DateTime::class); - self::assertInstanceOf(DateTime::class, $dateTime); + $this->assertInstanceOf(DateTime::class, $dateTime); } public function invalidFactories() @@ -520,7 +514,7 @@ public function testPassingInvalidAbstractFactoryTypeViaConfigurationRaisesExcep ) { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage($contains); - $this->createContainer([ + $serviceManager = $this->createContainer([ 'abstract_factories' => [ $factory, ], @@ -539,15 +533,15 @@ public function testCanSpecifyInitializerUsingStringViaConfiguration() ]); $instance = $serviceManager->get(stdClass::class); - self::assertInstanceOf(stdClass::class, $instance); - self::assertTrue(isset($instance->foo), '"foo" property was not injected by initializer'); - self::assertEquals('bar', $instance->foo, '"foo" property was not properly injected'); + $this->assertInstanceOf(stdClass::class, $instance); + $this->assertTrue(isset($instance->foo), '"foo" property was not injected by initializer'); + $this->assertEquals('bar', $instance->foo, '"foo" property was not properly injected'); } public function invalidInitializers() { $factories = $this->invalidFactories(); - $factories['non-class-string'] = ['non-callable-string', 'callable or an instance of']; + $factories['non-class-string'] = ['non-callable-string', 'valid function name or class name']; return $factories; } @@ -561,7 +555,7 @@ public function testPassingInvalidInitializerTypeViaConfigurationRaisesException ) { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage($contains); - $this->createContainer([ + $serviceManager = $this->createContainer([ 'initializers' => [ $initializer, ], @@ -595,6 +589,9 @@ public function testInvalidDelegatorShouldRaiseExceptionDuringCreation( $delegator, $contains = 'non-callable delegator' ) { + $config = [ + 'option' => 'OPTIONED', + ]; $serviceManager = $this->createContainer([ 'factories' => [ stdClass::class => InvokableFactory::class, @@ -629,9 +626,9 @@ public function testCanInjectAliases() $foo = $container->get('foo'); $bar = $container->get('bar'); - self::assertInstanceOf(stdClass::class, $foo); - self::assertInstanceOf(stdClass::class, $bar); - self::assertSame($foo, $bar); + $this->assertInstanceOf(stdClass::class, $foo); + $this->assertInstanceOf(stdClass::class, $bar); + $this->assertSame($foo, $bar); } /** @@ -642,10 +639,9 @@ public function testCanInjectInvokables() { $container = $this->createContainer(); $container->setInvokableClass('foo', stdClass::class); - self::assertTrue($container->has('foo')); - self::assertTrue($container->has(stdClass::class)); + $this->assertTrue($container->has('foo')); $foo = $container->get('foo'); - self::assertInstanceOf(stdClass::class, $foo); + $this->assertInstanceOf(stdClass::class, $foo); } /** @@ -660,9 +656,9 @@ public function testCanInjectFactories() $container->setFactory('foo', function () use ($instance) { return $instance; }); - self::assertTrue($container->has('foo')); + $this->assertTrue($container->has('foo')); $foo = $container->get('foo'); - self::assertSame($instance, $foo); + $this->assertSame($instance, $foo); } /** @@ -676,9 +672,9 @@ public function testCanMapLazyServices() $r = new ReflectionProperty($container, 'lazyServices'); $r->setAccessible(true); $lazyServices = $r->getValue($container); - self::assertArrayHasKey('class_map', $lazyServices); - self::assertArrayHasKey('foo', $lazyServices['class_map']); - self::assertEquals(__CLASS__, $lazyServices['class_map']['foo']); + $this->assertArrayHasKey('class_map', $lazyServices); + $this->assertArrayHasKey('foo', $lazyServices['class_map']); + $this->assertEquals(__CLASS__, $lazyServices['class_map']['foo']); } /** @@ -690,9 +686,9 @@ public function testCanInjectAbstractFactories() $container = $this->createContainer(); $container->addAbstractFactory(TestAsset\SimpleAbstractFactory::class); // @todo Remove "true" flag once #49 is merged - self::assertTrue($container->has(stdClass::class, true)); + $this->assertTrue($container->has(stdClass::class, true)); $instance = $container->get(stdClass::class); - self::assertInstanceOf(stdClass::class, $instance); + $this->assertInstanceOf(stdClass::class, $instance); } /** @@ -715,8 +711,8 @@ public function testCanInjectDelegators() }); $foo = $container->get('foo'); - self::assertInstanceOf(stdClass::class, $foo); - self::assertAttributeEquals('foo', 'name', $foo); + $this->assertInstanceOf(stdClass::class, $foo); + $this->assertAttributeEquals('foo', 'name', $foo); } /** @@ -741,8 +737,8 @@ public function testCanInjectInitializers() }); $foo = $container->get('foo'); - self::assertInstanceOf(stdClass::class, $foo); - self::assertAttributeEquals(stdClass::class, 'name', $foo); + $this->assertInstanceOf(stdClass::class, $foo); + $this->assertAttributeEquals(stdClass::class, 'name', $foo); } /** @@ -753,7 +749,7 @@ public function testCanInjectServices() { $container = $this->createContainer(); $container->setService('foo', $this); - self::assertSame($this, $container->get('foo')); + $this->assertSame($this, $container->get('foo')); } /** @@ -772,7 +768,7 @@ public function testCanInjectSharingRules() $container->setShared('foo', false); $first = $container->get('foo'); $second = $container->get('foo'); - self::assertNotSame($first, $second); + $this->assertNotSame($first, $second); } public function methodsAffectedByOverrideSettings() @@ -816,7 +812,7 @@ public function testConfiguringInstanceRaisesExceptionIfAllowOverrideIsFalse($me public function testAllowOverrideFlagIsFalseByDefault() { $container = $this->createContainer(); - self::assertFalse($container->getAllowOverride()); + $this->assertFalse($container->getAllowOverride()); return $container; } @@ -827,7 +823,7 @@ public function testAllowOverrideFlagIsFalseByDefault() public function testAllowOverrideFlagIsMutable($container) { $container->setAllowOverride(true); - self::assertTrue($container->getAllowOverride()); + $this->assertTrue($container->getAllowOverride()); } /** @@ -837,9 +833,9 @@ public function testCanRetrieveParentContainerViaGetServiceLocatorWithDeprecatio { $container = $this->createContainer(); set_error_handler(function ($errno, $errstr) { - self::assertEquals(E_USER_DEPRECATED, $errno); + $this->assertEquals(E_USER_DEPRECATED, $errno); }, E_USER_DEPRECATED); - self::assertSame($this->creationContext, $container->getServiceLocator()); + $this->assertSame($this->creationContext, $container->getServiceLocator()); restore_error_handler(); } @@ -857,75 +853,4 @@ public function testCrashesOnCyclicAliases() ], ]); } - - public function testMinimalCyclicAliasDefinitionShouldThrow() - { - $sm = $this->createContainer([]); - - $this->expectException(CyclicAliasException::class); - $sm->setAlias('alias', 'alias'); - } - - public function testCoverageDepthFirstTaggingOnRecursiveAliasDefinitions() - { - $sm = $this->createContainer([ - 'factories' => [ - stdClass::class => InvokableFactory::class, - ], - 'aliases' => [ - 'alias1' => 'alias2', - 'alias2' => 'alias3', - 'alias3' => stdClass::class, - ], - ]); - $this->assertSame($sm->get('alias1'), $sm->get('alias2')); - $this->assertSame($sm->get(stdClass::class), $sm->get('alias1')); - } - - public function testMemberBasedConfigurationGetsApplied() - { - $sm = new PreconfiguredServiceManager(); - - // will be true if $aliases array is properly setup and - // recursive alias resolution works - $this->assertTrue($sm->has('alias1')); - $this->assertInstanceOf(stdClass::class, $sm->get('alias1')); - - // will be true if $aliases array is properly setup and - // simple alias resolution works - $this->assertTrue($sm->has('alias2')); - $this->assertInstanceOf(stdClass::class, $sm->get('alias2')); - - // will return true if $services array is properly setup - $this->assertTrue($sm->has('service')); - $this->assertInstanceOf(stdClass::class, $sm->get('service')); - - // will be true if factory array is properly setup - $this->assertTrue($sm->has('delegator')); - $this->assertInstanceOf(InvokableObject::class, $sm->get('delegator')); - - // will be true if initializer is present - $this->assertTrue($sm->get('delegator')->initializerPresent); - - // will be true if factory array is properly setup - $this->assertTrue($sm->has('factory')); - $this->assertInstanceOf(InvokableObject::class, $sm->get('factory')); - - // will be true if initializer is present - $this->assertTrue($sm->get('factory')->initializerPresent); - - // will succeed if invokable is properly set up - $this->assertTrue($sm->has('invokable')); - $this->assertInstanceOf(stdClass::class, $sm->get('invokable')); - - // will be true if initializer is present - $this->assertTrue($sm->get('invokable')->initializerPresent); - - // will succeed if abstract factory is available - $this->assertTrue($sm->has('foo')); - $this->assertInstanceOf(Foo::class, $sm->get('foo')); - - // will be true if initializer is present - $this->assertTrue($sm->get('foo')->initializerPresent); - } }