├── src ├── Exception │ ├── ContainerModificationsNotAllowedException.php │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ ├── InvalidServiceException.php │ ├── ServiceNotCreatedException.php │ ├── ServiceNotFoundException.php │ └── CyclicAliasException.php ├── Initializer │ └── InitializerInterface.php ├── PluginManagerInterface.php ├── Factory │ ├── InvokableFactory.php │ ├── AbstractFactoryInterface.php │ ├── FactoryInterface.php │ └── DelegatorFactoryInterface.php ├── ServiceLocatorInterface.php ├── PsrContainerDecorator.php ├── ConfigInterface.php ├── FactoryInterface.php ├── InitializerInterface.php ├── DelegatorFactoryInterface.php ├── Proxy │ └── LazyServiceFactory.php ├── AbstractFactoryInterface.php ├── AbstractFactory │ ├── ConfigAbstractFactory.php │ └── ReflectionBasedAbstractFactory.php ├── Config.php ├── Test │ └── CommonPluginManagerTrait.php ├── Tool │ ├── FactoryCreator.php │ ├── FactoryCreatorCommand.php │ ├── ConfigDumperCommand.php │ └── ConfigDumper.php ├── AbstractPluginManager.php └── ServiceManager.php ├── bin ├── generate-factory-for-class └── generate-deps-for-config-factory ├── LICENSE.md ├── README.md ├── composer.json └── CHANGELOG.md /src/Exception/ContainerModificationsNotAllowedException.php: -------------------------------------------------------------------------------- 1 | container = $container; 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function get($id) 32 | { 33 | return $this->container->get($id); 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function has($id) 40 | { 41 | return $this->container->has($id); 42 | } 43 | 44 | /** 45 | * @return PsrContainerInterface 46 | */ 47 | public function getContainer() 48 | { 49 | return $this->container; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Factory/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-servicemanager](https://github.com/laminas/laminas-servicemanager). 6 | 7 | Master: 8 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-servicemanager.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-servicemanager) 9 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-servicemanager/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-servicemanager?branch=master) 10 | Develop: 11 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-servicemanager.svg?branch=develop)](https://secure.travis-ci.org/zendframework/zend-servicemanager) 12 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-servicemanager/badge.svg?branch=develop)](https://coveralls.io/github/zendframework/zend-servicemanager?branch=develop) 13 | 14 | The Service Locator design pattern is implemented by the `Zend\ServiceManager` 15 | component. The Service Locator is a service/object locator, tasked with 16 | retrieving other objects. 17 | 18 | - File issues at https://github.com/zendframework/zend-servicemanager/issues 19 | - [Online documentation](https://docs.zendframework.com/zend-servicemanager) 20 | - [Documentation source files](docs/book/) 21 | 22 | ## Benchmarks 23 | 24 | We provide scripts for benchmarking zend-servicemanager using the 25 | [PHPBench](https://github.com/phpbench/phpbench) framework; these can be 26 | found in the `benchmarks/` directory. 27 | 28 | To execute the benchmarks you can run the following command: 29 | 30 | ```bash 31 | $ vendor/bin/phpbench run --report=aggregate 32 | ``` 33 | -------------------------------------------------------------------------------- /src/DelegatorFactoryInterface.php: -------------------------------------------------------------------------------- 1 | proxyFactory = $proxyFactory; 42 | $this->servicesMap = $servicesMap; 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | * 48 | * @return \ProxyManager\Proxy\VirtualProxyInterface 49 | */ 50 | public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null) 51 | { 52 | if (isset($this->servicesMap[$name])) { 53 | $initializer = function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($callback) { 54 | $proxy->setProxyInitializer(null); 55 | $wrappedInstance = $callback(); 56 | 57 | return true; 58 | }; 59 | 60 | return $this->proxyFactory->createProxy($this->servicesMap[$name], $initializer); 61 | } 62 | 63 | throw new Exception\ServiceNotFoundException( 64 | sprintf('The requested service "%s" was not found in the provided services map', $name) 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/AbstractFactoryInterface.php: -------------------------------------------------------------------------------- 1 | has('config') || ! array_key_exists(self::class, $container->get('config'))) { 25 | return false; 26 | } 27 | $config = $container->get('config'); 28 | $dependencies = $config[self::class]; 29 | 30 | return is_array($dependencies) && array_key_exists($requestedName, $dependencies); 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | public function __invoke(\Interop\Container\ContainerInterface $container, $requestedName, array $options = null) 37 | { 38 | if (! $container->has('config')) { 39 | throw new ServiceNotCreatedException('Cannot find a config array in the container'); 40 | } 41 | 42 | $config = $container->get('config'); 43 | 44 | if (! (is_array($config) || $config instanceof ArrayObject)) { 45 | throw new ServiceNotCreatedException('Config must be an array or an instance of ArrayObject'); 46 | } 47 | 48 | if (! array_key_exists(self::class, $config)) { 49 | throw new ServiceNotCreatedException('Cannot find a `' . self::class . '` key in the config array'); 50 | } 51 | 52 | $dependencies = $config[self::class]; 53 | 54 | if (! is_array($dependencies) 55 | || ! array_key_exists($requestedName, $dependencies) 56 | || ! is_array($dependencies[$requestedName]) 57 | ) { 58 | throw new ServiceNotCreatedException('Dependencies config must exist and be an array'); 59 | } 60 | 61 | $serviceDependencies = $dependencies[$requestedName]; 62 | 63 | if ($serviceDependencies !== array_values(array_map('strval', $serviceDependencies))) { 64 | $problem = json_encode(array_map('gettype', $serviceDependencies)); 65 | throw new ServiceNotCreatedException('Service message must be an array of strings, ' . $problem . ' given'); 66 | } 67 | 68 | $arguments = array_map([$container, 'get'], $serviceDependencies); 69 | 70 | return new $requestedName(...$arguments); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | true, 35 | 'aliases' => true, 36 | 'delegators' => true, 37 | 'factories' => true, 38 | 'initializers' => true, 39 | 'invokables' => true, 40 | 'lazy_services' => true, 41 | 'services' => true, 42 | 'shared' => true, 43 | ]; 44 | 45 | /** 46 | * @var array 47 | */ 48 | protected $config = [ 49 | 'abstract_factories' => [], 50 | 'aliases' => [], 51 | 'delegators' => [], 52 | 'factories' => [], 53 | 'initializers' => [], 54 | 'invokables' => [], 55 | 'lazy_services' => [], 56 | 'services' => [], 57 | 'shared' => [], 58 | ]; 59 | 60 | /** 61 | * @param array $config 62 | */ 63 | public function __construct(array $config = []) 64 | { 65 | // Only merge keys we're interested in 66 | foreach (array_keys($config) as $key) { 67 | if (! isset($this->allowedKeys[$key])) { 68 | unset($config[$key]); 69 | } 70 | } 71 | $this->config = $this->merge($this->config, $config); 72 | } 73 | 74 | /** 75 | * @inheritdoc 76 | */ 77 | public function configureServiceManager(ServiceManager $serviceManager) 78 | { 79 | return $serviceManager->configure($this->config); 80 | } 81 | 82 | /** 83 | * @inheritdoc 84 | */ 85 | public function toArray() 86 | { 87 | return $this->config; 88 | } 89 | 90 | /** 91 | * Copy paste from https://github.com/zendframework/zend-stdlib/commit/26fcc32a358aa08de35625736095cb2fdaced090 92 | * to keep compatibility with previous version 93 | * 94 | * @link https://github.com/zendframework/zend-servicemanager/pull/68 95 | */ 96 | private function merge(array $a, array $b) 97 | { 98 | foreach ($b as $key => $value) { 99 | if ($value instanceof MergeReplaceKeyInterface) { 100 | $a[$key] = $value->getData(); 101 | } elseif (isset($a[$key]) || array_key_exists($key, $a)) { 102 | if ($value instanceof MergeRemoveKey) { 103 | unset($a[$key]); 104 | } elseif (is_int($key)) { 105 | $a[] = $value; 106 | } elseif (is_array($value) && is_array($a[$key])) { 107 | $a[$key] = $this->merge($a[$key], $value); 108 | } else { 109 | $a[$key] = $value; 110 | } 111 | } else { 112 | if (! $value instanceof MergeRemoveKey) { 113 | $a[$key] = $value; 114 | } 115 | } 116 | } 117 | return $a; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Test/CommonPluginManagerTrait.php: -------------------------------------------------------------------------------- 1 | getPluginManager(); 26 | $reflection = new ReflectionProperty($manager, 'instanceOf'); 27 | $reflection->setAccessible(true); 28 | $this->assertEquals($this->getInstanceOf(), $reflection->getValue($manager), 'instanceOf does not match'); 29 | } 30 | 31 | public function testShareByDefaultAndSharedByDefault() 32 | { 33 | $manager = $this->getPluginManager(); 34 | $reflection = new ReflectionClass($manager); 35 | $shareByDefault = $sharedByDefault = true; 36 | 37 | foreach ($reflection->getProperties() as $prop) { 38 | if ($prop->getName() == 'shareByDefault') { 39 | $prop->setAccessible(true); 40 | $shareByDefault = $prop->getValue($manager); 41 | } 42 | if ($prop->getName() == 'sharedByDefault') { 43 | $prop->setAccessible(true); 44 | $sharedByDefault = $prop->getValue($manager); 45 | } 46 | } 47 | 48 | $this->assertTrue( 49 | $shareByDefault == $sharedByDefault, 50 | 'Values of shareByDefault and sharedByDefault do not match' 51 | ); 52 | } 53 | 54 | public function testRegisteringInvalidElementRaisesException() 55 | { 56 | $this->expectException($this->getServiceNotFoundException()); 57 | $this->getPluginManager()->setService('test', $this); 58 | } 59 | 60 | public function testLoadingInvalidElementRaisesException() 61 | { 62 | $manager = $this->getPluginManager(); 63 | $manager->setInvokableClass('test', get_class($this)); 64 | $this->expectException($this->getServiceNotFoundException()); 65 | $manager->get('test'); 66 | } 67 | 68 | /** 69 | * @dataProvider aliasProvider 70 | */ 71 | public function testPluginAliasesResolve($alias, $expected) 72 | { 73 | $this->assertInstanceOf($expected, $this->getPluginManager()->get($alias), "Alias '$alias' does not resolve'"); 74 | } 75 | 76 | public function aliasProvider() 77 | { 78 | $manager = $this->getPluginManager(); 79 | $reflection = new ReflectionProperty($manager, 'aliases'); 80 | $reflection->setAccessible(true); 81 | $data = []; 82 | foreach ($reflection->getValue($manager) as $alias => $expected) { 83 | $data[] = [$alias, $expected]; 84 | } 85 | return $data; 86 | } 87 | 88 | protected function getServiceNotFoundException() 89 | { 90 | $manager = $this->getPluginManager(); 91 | if (method_exists($manager, 'configure')) { 92 | return InvalidServiceException::class; 93 | } 94 | return $this->getV2InvalidPluginException(); 95 | } 96 | 97 | /** 98 | * Returns the plugin manager to test 99 | * @return \Zend\ServiceManager\AbstractPluginManager 100 | */ 101 | abstract protected function getPluginManager(); 102 | 103 | /** 104 | * Returns the FQCN of the exception thrown under v2 by `validatePlugin()` 105 | * @return mixed 106 | */ 107 | abstract protected function getV2InvalidPluginException(); 108 | 109 | /** 110 | * Returns the value the instanceOf property has been set to 111 | * @return string 112 | */ 113 | abstract protected function getInstanceOf(); 114 | } 115 | -------------------------------------------------------------------------------- /src/Exception/CyclicAliasException.php: -------------------------------------------------------------------------------- 1 | $reference) { 77 | $map[] = '"' . $alias . '" => "' . $reference . '"'; 78 | } 79 | 80 | return "[\n" . implode("\n", $map) . "\n]"; 81 | } 82 | 83 | /** 84 | * @param string[][] $detectedCycles 85 | * 86 | * @return string 87 | */ 88 | private static function printCycles(array $detectedCycles) 89 | { 90 | return "[\n" . implode("\n", array_map([__CLASS__, 'printCycle'], $detectedCycles)) . "\n]"; 91 | } 92 | 93 | /** 94 | * @param string[] $detectedCycle 95 | * 96 | * @return string 97 | */ 98 | private static function printCycle(array $detectedCycle) 99 | { 100 | $fullCycle = array_keys($detectedCycle); 101 | $fullCycle[] = reset($fullCycle); 102 | 103 | return implode( 104 | ' => ', 105 | array_map( 106 | function ($cycle) { 107 | return '"' . $cycle . '"'; 108 | }, 109 | $fullCycle 110 | ) 111 | ); 112 | } 113 | 114 | /** 115 | * @param bool[][] $detectedCycles 116 | * 117 | * @return bool[][] de-duplicated 118 | */ 119 | private static function deDuplicateDetectedCycles(array $detectedCycles) 120 | { 121 | $detectedCyclesByHash = []; 122 | 123 | foreach ($detectedCycles as $detectedCycle) { 124 | $cycleAliases = array_keys($detectedCycle); 125 | 126 | sort($cycleAliases); 127 | 128 | $hash = serialize(array_values($cycleAliases)); 129 | 130 | $detectedCyclesByHash[$hash] = isset($detectedCyclesByHash[$hash]) 131 | ? $detectedCyclesByHash[$hash] 132 | : $detectedCycle; 133 | } 134 | 135 | return array_values($detectedCyclesByHash); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Tool/FactoryCreator.php: -------------------------------------------------------------------------------- 1 | getClassName($className); 48 | 49 | return sprintf( 50 | self::FACTORY_TEMPLATE, 51 | str_replace('\\' . $class, '', $className), 52 | $className, 53 | $class, 54 | $class, 55 | $class, 56 | $this->createArgumentString($className) 57 | ); 58 | } 59 | 60 | /** 61 | * @param $className 62 | * @return string 63 | */ 64 | private function getClassName($className) 65 | { 66 | $class = substr($className, strrpos($className, '\\') + 1); 67 | return $class; 68 | } 69 | 70 | /** 71 | * @param string $className 72 | * @return array 73 | */ 74 | private function getConstructorParameters($className) 75 | { 76 | $reflectionClass = new ReflectionClass($className); 77 | 78 | if (! $reflectionClass || ! $reflectionClass->getConstructor()) { 79 | return []; 80 | } 81 | 82 | $constructorParameters = $reflectionClass->getConstructor()->getParameters(); 83 | 84 | if (empty($constructorParameters)) { 85 | return []; 86 | } 87 | 88 | $constructorParameters = array_filter( 89 | $constructorParameters, 90 | function (ReflectionParameter $argument) { 91 | if ($argument->isOptional()) { 92 | return false; 93 | } 94 | 95 | if (null === $argument->getClass()) { 96 | throw new InvalidArgumentException(sprintf( 97 | 'Cannot identify type for constructor argument "%s"; ' 98 | . 'no type hint, or non-class/interface type hint', 99 | $argument->getName() 100 | )); 101 | } 102 | 103 | return true; 104 | } 105 | ); 106 | 107 | if (empty($constructorParameters)) { 108 | return []; 109 | } 110 | 111 | return array_map(function (ReflectionParameter $parameter) { 112 | return $parameter->getClass()->getName(); 113 | }, $constructorParameters); 114 | } 115 | 116 | /** 117 | * @param string $className 118 | * @return string 119 | */ 120 | private function createArgumentString($className) 121 | { 122 | $arguments = array_map(function ($dependency) { 123 | return sprintf('$container->get(\\%s::class)', $dependency); 124 | }, $this->getConstructorParameters($className)); 125 | 126 | switch (count($arguments)) { 127 | case 0: 128 | return ''; 129 | case 1: 130 | return array_shift($arguments); 131 | default: 132 | $argumentPad = str_repeat(' ', 12); 133 | $closePad = str_repeat(' ', 8); 134 | return sprintf( 135 | "\n%s%s\n%s", 136 | $argumentPad, 137 | implode(",\n" . $argumentPad, $arguments), 138 | $closePad 139 | ); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Tool/FactoryCreatorCommand.php: -------------------------------------------------------------------------------- 1 | Usage: 23 | 24 | %s [-h|--help|help] 25 | 26 | Arguments: 27 | 28 | -h|--help|help This usage message 29 | Name of the class to reflect and for which to generate 30 | a factory. 31 | 32 | Generates to STDOUT a factory for creating the specified class; this may then 33 | be added to your application, and configured as a factory for the class. 34 | EOH; 35 | 36 | /** 37 | * @var ConsoleHelper 38 | */ 39 | private $helper; 40 | 41 | /** 42 | * @var string 43 | */ 44 | private $scriptName; 45 | 46 | /** 47 | * @param string $scriptName 48 | * @param ConsoleHelper $helper 49 | */ 50 | public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ConsoleHelper $helper = null) 51 | { 52 | $this->scriptName = $scriptName; 53 | $this->helper = $helper ?: new ConsoleHelper(); 54 | } 55 | 56 | /** 57 | * @param array $args Argument list, minus script name 58 | * @return int Exit status 59 | */ 60 | public function __invoke(array $args) 61 | { 62 | $arguments = $this->parseArgs($args); 63 | 64 | switch ($arguments->command) { 65 | case self::COMMAND_HELP: 66 | $this->help(); 67 | return 0; 68 | case self::COMMAND_ERROR: 69 | $this->helper->writeErrorMessage($arguments->message); 70 | $this->help(STDERR); 71 | return 1; 72 | case self::COMMAND_DUMP: 73 | // fall-through 74 | default: 75 | break; 76 | } 77 | 78 | $generator = new FactoryCreator(); 79 | try { 80 | $factory = $generator->createFactory($arguments->class); 81 | } catch (Exception\InvalidArgumentException $e) { 82 | $this->helper->writeErrorMessage(sprintf( 83 | 'Unable to create factory for "%s": %s', 84 | $arguments->class, 85 | $e->getMessage() 86 | )); 87 | $this->help(STDERR); 88 | return 1; 89 | } 90 | 91 | $this->helper->write($factory, false); 92 | return 0; 93 | } 94 | 95 | /** 96 | * @param array $args 97 | * @return \stdClass 98 | */ 99 | private function parseArgs(array $args) 100 | { 101 | if (! count($args)) { 102 | return $this->createArguments(self::COMMAND_HELP); 103 | } 104 | 105 | $arg1 = array_shift($args); 106 | 107 | if (in_array($arg1, ['-h', '--help', 'help'], true)) { 108 | return $this->createArguments(self::COMMAND_HELP); 109 | } 110 | 111 | $class = $arg1; 112 | 113 | if (! class_exists($class)) { 114 | return $this->createArguments(self::COMMAND_ERROR, null, sprintf( 115 | 'Class "%s" does not exist or could not be autoloaded.', 116 | $class 117 | )); 118 | } 119 | 120 | return $this->createArguments(self::COMMAND_DUMP, $class); 121 | } 122 | 123 | /** 124 | * @param resource $resource Defaults to STDOUT 125 | * @return void 126 | */ 127 | private function help($resource = STDOUT) 128 | { 129 | $this->helper->writeLine(sprintf( 130 | self::HELP_TEMPLATE, 131 | $this->scriptName 132 | ), true, $resource); 133 | } 134 | 135 | /** 136 | * @param string $command 137 | * @param string|null $class Name of class to reflect. 138 | * @param string|null $error Error message, if any. 139 | * @return \stdClass 140 | */ 141 | private function createArguments($command, $class = null, $error = null) 142 | { 143 | return (object) [ 144 | 'command' => $command, 145 | 'class' => $class, 146 | 'message' => $error, 147 | ]; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Tool/ConfigDumperCommand.php: -------------------------------------------------------------------------------- 1 | Usage: 23 | 24 | %s [-h|--help|help] [-i|--ignore-unresolved] 25 | 26 | Arguments: 27 | 28 | -h|--help|help This usage message 29 | -i|--ignore-unresolved Ignore classes with unresolved direct dependencies. 30 | Path to a config file for which to generate 31 | configuration. If the file does not exist, it will 32 | be created. If it does exist, it must return an 33 | array, and the file will be updated with new 34 | configuration. 35 | Name of the class to reflect and for which to 36 | generate dependency configuration. 37 | 38 | Reads the provided configuration file (creating it if it does not exist), 39 | and injects it with ConfigAbstractFactory dependency configuration for 40 | the provided class name, writing the changes back to the file. 41 | EOH; 42 | 43 | /** 44 | * @var ConsoleHelper 45 | */ 46 | private $helper; 47 | 48 | /** 49 | * @var string 50 | */ 51 | private $scriptName; 52 | 53 | /** 54 | * @param string $scriptName 55 | */ 56 | public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ConsoleHelper $helper = null) 57 | { 58 | $this->scriptName = $scriptName; 59 | $this->helper = $helper ?: new ConsoleHelper(); 60 | } 61 | 62 | /** 63 | * @param array $args Argument list, minus script name 64 | * @return int Exit status 65 | */ 66 | public function __invoke(array $args) 67 | { 68 | $arguments = $this->parseArgs($args); 69 | 70 | switch ($arguments->command) { 71 | case self::COMMAND_HELP: 72 | $this->help(); 73 | return 0; 74 | case self::COMMAND_ERROR: 75 | $this->helper->writeErrorMessage($arguments->message); 76 | $this->help(STDERR); 77 | return 1; 78 | case self::COMMAND_DUMP: 79 | // fall-through 80 | default: 81 | break; 82 | } 83 | 84 | $dumper = new ConfigDumper(); 85 | try { 86 | $config = $dumper->createDependencyConfig( 87 | $arguments->config, 88 | $arguments->class, 89 | $arguments->ignoreUnresolved 90 | ); 91 | } catch (Exception\InvalidArgumentException $e) { 92 | $this->helper->writeErrorMessage(sprintf( 93 | 'Unable to create config for "%s": %s', 94 | $arguments->class, 95 | $e->getMessage() 96 | )); 97 | $this->help(STDERR); 98 | return 1; 99 | } 100 | 101 | file_put_contents($arguments->configFile, $dumper->dumpConfigFile($config)); 102 | 103 | $this->helper->writeLine(sprintf( 104 | '[DONE] Changes written to %s', 105 | $arguments->configFile 106 | )); 107 | return 0; 108 | } 109 | 110 | /** 111 | * @param array $args 112 | * @return \stdClass 113 | */ 114 | private function parseArgs(array $args) 115 | { 116 | if (! count($args)) { 117 | return $this->createHelpArgument(); 118 | } 119 | 120 | $arg1 = array_shift($args); 121 | 122 | if (in_array($arg1, ['-h', '--help', 'help'], true)) { 123 | return $this->createHelpArgument(); 124 | } 125 | 126 | $ignoreUnresolved = false; 127 | if (in_array($arg1, ['-i', '--ignore-unresolved'], true)) { 128 | $ignoreUnresolved = true; 129 | $arg1 = array_shift($args); 130 | } 131 | 132 | if (! count($args)) { 133 | return $this->createErrorArgument('Missing class name'); 134 | } 135 | 136 | $configFile = $arg1; 137 | switch (file_exists($configFile)) { 138 | case true: 139 | $config = require $configFile; 140 | 141 | if (! is_array($config)) { 142 | return $this->createErrorArgument(sprintf( 143 | 'Configuration at path "%s" does not return an array.', 144 | $configFile 145 | )); 146 | } 147 | 148 | break; 149 | case false: 150 | // fall-through 151 | default: 152 | if (! is_writable(dirname($configFile))) { 153 | return $this->createErrorArgument(sprintf( 154 | 'Cannot create configuration at path "%s"; not writable.', 155 | $configFile 156 | )); 157 | } 158 | 159 | $config = []; 160 | break; 161 | } 162 | 163 | $class = array_shift($args); 164 | 165 | if (! class_exists($class)) { 166 | return $this->createErrorArgument(sprintf( 167 | 'Class "%s" does not exist or could not be autoloaded.', 168 | $class 169 | )); 170 | } 171 | 172 | return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class, $ignoreUnresolved); 173 | } 174 | 175 | /** 176 | * @param resource $resource Defaults to STDOUT 177 | * @return void 178 | */ 179 | private function help($resource = STDOUT) 180 | { 181 | $this->helper->writeLine(sprintf( 182 | self::HELP_TEMPLATE, 183 | $this->scriptName 184 | ), true, $resource); 185 | } 186 | 187 | /** 188 | * @param string $command 189 | * @param string $configFile File from which config originates, and to 190 | * which it will be written. 191 | * @param array $config Parsed configuration. 192 | * @param string $class Name of class to reflect. 193 | * @param bool $ignoreUnresolved If to ignore classes with unresolved direct dependencies. 194 | * @return \stdClass 195 | */ 196 | private function createArguments($command, $configFile, $config, $class, $ignoreUnresolved) 197 | { 198 | return (object) [ 199 | 'command' => $command, 200 | 'configFile' => $configFile, 201 | 'config' => $config, 202 | 'class' => $class, 203 | 'ignoreUnresolved' => $ignoreUnresolved, 204 | ]; 205 | } 206 | 207 | /** 208 | * @param string $message 209 | * @return \stdClass 210 | */ 211 | private function createErrorArgument($message) 212 | { 213 | return (object) [ 214 | 'command' => self::COMMAND_ERROR, 215 | 'message' => $message, 216 | ]; 217 | } 218 | 219 | /** 220 | * @return \stdClass 221 | */ 222 | private function createHelpArgument() 223 | { 224 | return (object) [ 225 | 'command' => self::COMMAND_HELP, 226 | ]; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/AbstractPluginManager.php: -------------------------------------------------------------------------------- 1 | toArray(); 87 | } 88 | 89 | parent::__construct($config); 90 | 91 | if (! $configInstanceOrParentLocator instanceof ContainerInterface) { 92 | trigger_error(sprintf( 93 | '%s now expects a %s instance representing the parent container; please update your code', 94 | __METHOD__, 95 | ContainerInterface::class 96 | ), E_USER_DEPRECATED); 97 | } 98 | 99 | $this->creationContext = $configInstanceOrParentLocator instanceof ContainerInterface 100 | ? $configInstanceOrParentLocator 101 | : $this; 102 | } 103 | 104 | /** 105 | * Override configure() to validate service instances. 106 | * 107 | * If an instance passed in the `services` configuration is invalid for the 108 | * plugin manager, this method will raise an InvalidServiceException. 109 | * 110 | * {@inheritDoc} 111 | * @throws InvalidServiceException 112 | */ 113 | public function configure(array $config) 114 | { 115 | if (isset($config['services'])) { 116 | foreach ($config['services'] as $service) { 117 | $this->validate($service); 118 | } 119 | } 120 | 121 | parent::configure($config); 122 | 123 | return $this; 124 | } 125 | 126 | /** 127 | * {@inheritDoc} 128 | * 129 | * @param string $name Service name of plugin to retrieve. 130 | * @param null|array $options Options to use when creating the instance. 131 | * @return mixed 132 | * @throws Exception\ServiceNotFoundException if the manager does not have 133 | * a service definition for the instance, and the service is not 134 | * auto-invokable. 135 | * @throws InvalidServiceException if the plugin created is invalid for the 136 | * plugin context. 137 | */ 138 | public function get($name, array $options = null) 139 | { 140 | if (! $this->has($name)) { 141 | if (! $this->autoAddInvokableClass || ! class_exists($name)) { 142 | throw new Exception\ServiceNotFoundException(sprintf( 143 | 'A plugin by the name "%s" was not found in the plugin manager %s', 144 | $name, 145 | get_class($this) 146 | )); 147 | } 148 | 149 | $this->setFactory($name, Factory\InvokableFactory::class); 150 | } 151 | 152 | $instance = empty($options) ? parent::get($name) : $this->build($name, $options); 153 | $this->validate($instance); 154 | return $instance; 155 | } 156 | 157 | /** 158 | * {@inheritDoc} 159 | */ 160 | public function validate($instance) 161 | { 162 | if (method_exists($this, 'validatePlugin')) { 163 | trigger_error(sprintf( 164 | '%s::validatePlugin() has been deprecated as of 3.0; please define validate() instead', 165 | get_class($this) 166 | ), E_USER_DEPRECATED); 167 | $this->validatePlugin($instance); 168 | return; 169 | } 170 | 171 | if (empty($this->instanceOf) || $instance instanceof $this->instanceOf) { 172 | return; 173 | } 174 | 175 | throw new InvalidServiceException(sprintf( 176 | 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received', 177 | __CLASS__, 178 | $this->instanceOf, 179 | is_object($instance) ? get_class($instance) : gettype($instance) 180 | )); 181 | } 182 | 183 | /** 184 | * Implemented for backwards compatibility only. 185 | * 186 | * Returns the creation context. 187 | * 188 | * @deprecated since 3.0.0. The creation context should be passed during 189 | * instantiation instead. 190 | * @param ContainerInterface $container 191 | * @return void 192 | */ 193 | public function setServiceLocator(ContainerInterface $container) 194 | { 195 | trigger_error(sprintf( 196 | 'Usage of %s is deprecated since v3.0.0; please pass the container to the constructor instead', 197 | __METHOD__ 198 | ), E_USER_DEPRECATED); 199 | $this->creationContext = $container; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Tool/ConfigDumper.php: -------------------------------------------------------------------------------- 1 | container = $container; 40 | } 41 | 42 | /** 43 | * @param array $config 44 | * @param string $className 45 | * @param bool $ignoreUnresolved 46 | * @return array 47 | * @throws InvalidArgumentException for invalid $className 48 | */ 49 | public function createDependencyConfig(array $config, $className, $ignoreUnresolved = false) 50 | { 51 | $this->validateClassName($className); 52 | 53 | $reflectionClass = new ReflectionClass($className); 54 | 55 | // class is an interface; do nothing 56 | if ($reflectionClass->isInterface()) { 57 | return $config; 58 | } 59 | 60 | // class has no constructor, treat it as an invokable 61 | if (! $reflectionClass->getConstructor()) { 62 | return $this->createInvokable($config, $className); 63 | } 64 | 65 | $constructorArguments = $reflectionClass->getConstructor()->getParameters(); 66 | $constructorArguments = array_filter( 67 | $constructorArguments, 68 | function (ReflectionParameter $argument) { 69 | return ! $argument->isOptional(); 70 | } 71 | ); 72 | 73 | // has no required parameters, treat it as an invokable 74 | if (empty($constructorArguments)) { 75 | return $this->createInvokable($config, $className); 76 | } 77 | 78 | $classConfig = []; 79 | 80 | foreach ($constructorArguments as $constructorArgument) { 81 | $argumentType = $constructorArgument->getClass(); 82 | if (is_null($argumentType)) { 83 | if ($ignoreUnresolved) { 84 | // don't throw an exception, just return the previous config 85 | return $config; 86 | } 87 | // don't throw an exception if the class is an already defined service 88 | if ($this->container && $this->container->has($className)) { 89 | return $config; 90 | } 91 | throw new InvalidArgumentException(sprintf( 92 | 'Cannot create config for constructor argument "%s", ' 93 | . 'it has no type hint, or non-class/interface type hint', 94 | $constructorArgument->getName() 95 | )); 96 | } 97 | $argumentName = $argumentType->getName(); 98 | $config = $this->createDependencyConfig($config, $argumentName, $ignoreUnresolved); 99 | $classConfig[] = $argumentName; 100 | } 101 | 102 | $config[ConfigAbstractFactory::class][$className] = $classConfig; 103 | 104 | return $config; 105 | } 106 | 107 | /** 108 | * @param $className 109 | * @throws InvalidArgumentException if class name is not a string or does 110 | * not exist. 111 | */ 112 | private function validateClassName($className) 113 | { 114 | if (! is_string($className)) { 115 | throw new InvalidArgumentException('Class name must be a string, ' . gettype($className) . ' given'); 116 | } 117 | 118 | if (! class_exists($className) && ! interface_exists($className)) { 119 | throw new InvalidArgumentException('Cannot find class or interface with name ' . $className); 120 | } 121 | } 122 | 123 | /** 124 | * @param array $config 125 | * @param string $className 126 | * @return array 127 | */ 128 | private function createInvokable(array $config, $className) 129 | { 130 | $config[ConfigAbstractFactory::class][$className] = []; 131 | return $config; 132 | } 133 | 134 | /** 135 | * @param array $config 136 | * @return array 137 | * @throws InvalidArgumentException if ConfigAbstractFactory configuration 138 | * value is not an array. 139 | */ 140 | public function createFactoryMappingsFromConfig(array $config) 141 | { 142 | if (! array_key_exists(ConfigAbstractFactory::class, $config)) { 143 | return $config; 144 | } 145 | 146 | if (! is_array($config[ConfigAbstractFactory::class])) { 147 | throw new InvalidArgumentException( 148 | 'Config key for ' . ConfigAbstractFactory::class . ' should be an array, ' . gettype( 149 | $config[ConfigAbstractFactory::class] 150 | ) . ' given' 151 | ); 152 | } 153 | 154 | foreach ($config[ConfigAbstractFactory::class] as $className => $dependency) { 155 | $config = $this->createFactoryMappings($config, $className); 156 | } 157 | return $config; 158 | } 159 | 160 | /** 161 | * @param array $config 162 | * @param string $className 163 | * @return array 164 | */ 165 | public function createFactoryMappings(array $config, $className) 166 | { 167 | $this->validateClassName($className); 168 | 169 | if (array_key_exists('service_manager', $config) 170 | && array_key_exists('factories', $config['service_manager']) 171 | && array_key_exists($className, $config['service_manager']['factories']) 172 | ) { 173 | return $config; 174 | } 175 | 176 | $config['service_manager']['factories'][$className] = ConfigAbstractFactory::class; 177 | return $config; 178 | } 179 | 180 | /** 181 | * @param array $config 182 | * @return string 183 | */ 184 | public function dumpConfigFile(array $config) 185 | { 186 | $prepared = $this->prepareConfig($config); 187 | return sprintf( 188 | self::CONFIG_TEMPLATE, 189 | get_class($this), 190 | date('Y-m-d H:i:s'), 191 | $prepared 192 | ); 193 | } 194 | 195 | /** 196 | * @param array|Traversable $config 197 | * @param int $indentLevel 198 | * @return string 199 | */ 200 | private function prepareConfig($config, $indentLevel = 1) 201 | { 202 | $indent = str_repeat(' ', $indentLevel * 4); 203 | $entries = []; 204 | foreach ($config as $key => $value) { 205 | $key = $this->createConfigKey($key); 206 | $entries[] = sprintf( 207 | '%s%s%s,', 208 | $indent, 209 | $key ? sprintf('%s => ', $key) : '', 210 | $this->createConfigValue($value, $indentLevel) 211 | ); 212 | } 213 | 214 | $outerIndent = str_repeat(' ', ($indentLevel - 1) * 4); 215 | 216 | return sprintf( 217 | "[\n%s\n%s]", 218 | implode("\n", $entries), 219 | $outerIndent 220 | ); 221 | } 222 | 223 | /** 224 | * @param string|int|null $key 225 | * @return null|string 226 | */ 227 | private function createConfigKey($key) 228 | { 229 | if (is_string($key) && class_exists($key)) { 230 | return sprintf('\\%s::class', $key); 231 | } 232 | 233 | if (is_int($key)) { 234 | return null; 235 | } 236 | 237 | return sprintf("'%s'", $key); 238 | } 239 | 240 | /** 241 | * @param mixed $value 242 | * @param int $indentLevel 243 | * @return string 244 | */ 245 | private function createConfigValue($value, $indentLevel) 246 | { 247 | if (is_array($value) || $value instanceof Traversable) { 248 | return $this->prepareConfig($value, $indentLevel + 1); 249 | } 250 | 251 | if (is_string($value) && class_exists($value)) { 252 | return sprintf('\\%s::class', $value); 253 | } 254 | 255 | return var_export($value, true); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/AbstractFactory/ReflectionBasedAbstractFactory.php: -------------------------------------------------------------------------------- 1 | 27 | * 'service_manager' => [ 28 | * 'abstract_factories' => [ 29 | * ReflectionBasedAbstractFactory::class, 30 | * ], 31 | * ], 32 | * 33 | * 34 | * Or as a factory, mapping a class name to it: 35 | * 36 | * 37 | * 'service_manager' => [ 38 | * 'factories' => [ 39 | * MyClassWithDependencies::class => ReflectionBasedAbstractFactory::class, 40 | * ], 41 | * ], 42 | * 43 | * 44 | * The latter approach is more explicit, and also more performant. 45 | * 46 | * The factory has the following constraints/features: 47 | * 48 | * - A parameter named `$config` typehinted as an array will receive the 49 | * application "config" service (i.e., the merged configuration). 50 | * - Parameters type-hinted against array, but not named `$config` will 51 | * be injected with an empty array. 52 | * - Scalar parameters will result in an exception being thrown, unless 53 | * a default value is present; if the default is present, that will be used. 54 | * - If a service cannot be found for a given typehint, the factory will 55 | * raise an exception detailing this. 56 | * - Some services provided by Zend Framework components do not have 57 | * entries based on their class name (for historical reasons); the 58 | * factory allows defining a map of these class/interface names to the 59 | * corresponding service name to allow them to resolve. 60 | * 61 | * `$options` passed to the factory are ignored in all cases, as we cannot 62 | * make assumptions about which argument(s) they might replace. 63 | * 64 | * Based on the LazyControllerAbstractFactory from zend-mvc. 65 | */ 66 | class ReflectionBasedAbstractFactory implements AbstractFactoryInterface 67 | { 68 | /** 69 | * Maps known classes/interfaces to the service that provides them; only 70 | * required for those services with no entry based on the class/interface 71 | * name. 72 | * 73 | * Extend the class if you wish to add to the list. 74 | * 75 | * Example: 76 | * 77 | * 78 | * [ 79 | * \Zend\Filter\FilterPluginManager::class => 'FilterManager', 80 | * \Zend\Validator\ValidatorPluginManager::class => 'ValidatorManager', 81 | * ] 82 | * 83 | * 84 | * @var string[] 85 | */ 86 | protected $aliases = []; 87 | 88 | /** 89 | * Constructor. 90 | * 91 | * Allows overriding the internal list of aliases. These should be of the 92 | * form `class name => well-known service name`; see the documentation for 93 | * the `$aliases` property for details on what is accepted. 94 | * 95 | * @param string[] $aliases 96 | */ 97 | public function __construct(array $aliases = []) 98 | { 99 | if (! empty($aliases)) { 100 | $this->aliases = $aliases; 101 | } 102 | } 103 | 104 | /** 105 | * {@inheritDoc} 106 | * 107 | * @return DispatchableInterface 108 | */ 109 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) 110 | { 111 | $reflectionClass = new ReflectionClass($requestedName); 112 | 113 | if (null === ($constructor = $reflectionClass->getConstructor())) { 114 | return new $requestedName(); 115 | } 116 | 117 | $reflectionParameters = $constructor->getParameters(); 118 | 119 | if (empty($reflectionParameters)) { 120 | return new $requestedName(); 121 | } 122 | 123 | $resolver = $container->has('config') 124 | ? $this->resolveParameterWithConfigService($container, $requestedName) 125 | : $this->resolveParameterWithoutConfigService($container, $requestedName); 126 | 127 | $parameters = array_map($resolver, $reflectionParameters); 128 | 129 | return new $requestedName(...$parameters); 130 | } 131 | 132 | /** 133 | * {@inheritDoc} 134 | */ 135 | public function canCreate(ContainerInterface $container, $requestedName) 136 | { 137 | return class_exists($requestedName) && $this->canCallConstructor($requestedName); 138 | } 139 | 140 | private function canCallConstructor($requestedName) 141 | { 142 | $constructor = (new ReflectionClass($requestedName))->getConstructor(); 143 | 144 | return $constructor === null || $constructor->isPublic(); 145 | } 146 | 147 | /** 148 | * Resolve a parameter to a value. 149 | * 150 | * Returns a callback for resolving a parameter to a value, but without 151 | * allowing mapping array `$config` arguments to the `config` service. 152 | * 153 | * @param ContainerInterface $container 154 | * @param string $requestedName 155 | * @return callable 156 | */ 157 | private function resolveParameterWithoutConfigService(ContainerInterface $container, $requestedName) 158 | { 159 | /** 160 | * @param ReflectionParameter $parameter 161 | * @return mixed 162 | * @throws ServiceNotFoundException If type-hinted parameter cannot be 163 | * resolved to a service in the container. 164 | */ 165 | return function (ReflectionParameter $parameter) use ($container, $requestedName) { 166 | return $this->resolveParameter($parameter, $container, $requestedName); 167 | }; 168 | } 169 | 170 | /** 171 | * Returns a callback for resolving a parameter to a value, including mapping 'config' arguments. 172 | * 173 | * Unlike resolveParameter(), this version will detect `$config` array 174 | * arguments and have them return the 'config' service. 175 | * 176 | * @param ContainerInterface $container 177 | * @param string $requestedName 178 | * @return callable 179 | */ 180 | private function resolveParameterWithConfigService(ContainerInterface $container, $requestedName) 181 | { 182 | /** 183 | * @param ReflectionParameter $parameter 184 | * @return mixed 185 | * @throws ServiceNotFoundException If type-hinted parameter cannot be 186 | * resolved to a service in the container. 187 | */ 188 | return function (ReflectionParameter $parameter) use ($container, $requestedName) { 189 | if ($parameter->isArray() && $parameter->getName() === 'config') { 190 | return $container->get('config'); 191 | } 192 | return $this->resolveParameter($parameter, $container, $requestedName); 193 | }; 194 | } 195 | 196 | /** 197 | * Logic common to all parameter resolution. 198 | * 199 | * @param ReflectionParameter $parameter 200 | * @param ContainerInterface $container 201 | * @param string $requestedName 202 | * @return mixed 203 | * @throws ServiceNotFoundException If type-hinted parameter cannot be 204 | * resolved to a service in the container. 205 | */ 206 | private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName) 207 | { 208 | if ($parameter->isArray()) { 209 | return []; 210 | } 211 | 212 | if (! $parameter->getClass()) { 213 | if (! $parameter->isDefaultValueAvailable()) { 214 | throw new ServiceNotFoundException(sprintf( 215 | 'Unable to create service "%s"; unable to resolve parameter "%s" ' 216 | . 'to a class, interface, or array type', 217 | $requestedName, 218 | $parameter->getName() 219 | )); 220 | } 221 | 222 | return $parameter->getDefaultValue(); 223 | } 224 | 225 | $type = $parameter->getClass()->getName(); 226 | $type = isset($this->aliases[$type]) ? $this->aliases[$type] : $type; 227 | 228 | if ($container->has($type)) { 229 | return $container->get($type); 230 | } 231 | 232 | if (! $parameter->isOptional()) { 233 | throw new ServiceNotFoundException(sprintf( 234 | 'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"', 235 | $requestedName, 236 | $parameter->getName(), 237 | $type 238 | )); 239 | } 240 | 241 | // Type not available in container, but the value is optional and has a 242 | // default defined. 243 | return $parameter->getDefaultValue(); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 3.4.0 - 2018-12-22 6 | 7 | ### Added 8 | 9 | - [#275](https://github.com/zendframework/zend-servicemanager/pull/275) Enables 10 | plugin managers to accept as a creation context PSR Containers not 11 | implementing Interop interface 12 | 13 | ### Changed 14 | 15 | - Nothing. 16 | 17 | ### Deprecated 18 | 19 | - Nothing. 20 | 21 | ### Removed 22 | 23 | - Nothing. 24 | 25 | ### Fixed 26 | 27 | - [#268](https://github.com/zendframework/zend-servicemanager/pull/268) Fixes 28 | ReflectionBasedAbstractFactory trying to instantiate classes with private 29 | constructors 30 | 31 | ## 3.3.2 - 2018-01-29 32 | 33 | ### Added 34 | 35 | - Nothing. 36 | 37 | ### Changed 38 | 39 | - Nothing. 40 | 41 | ### Deprecated 42 | 43 | - Nothing. 44 | 45 | ### Removed 46 | 47 | - Nothing. 48 | 49 | ### Fixed 50 | 51 | - [#243](https://github.com/zendframework/zend-servicemanager/pull/243) provides 52 | a fix to the `ReflectionBasedAbstractFactory` to resolve type-hinted arguments 53 | with default values to their default values if no matching type is found in 54 | the container. 55 | 56 | - [#233](https://github.com/zendframework/zend-servicemanager/pull/233) fixes a 57 | number of parameter annotations to reflect the actual types used. 58 | 59 | ## 3.3.1 - 2017-11-27 60 | 61 | ### Added 62 | 63 | - [#201](https://github.com/zendframework/zend-servicemanager/pull/201) and 64 | [#202](https://github.com/zendframework/zend-servicemanager/pull/202) add 65 | support for PHP versions 7.1 and 7.2. 66 | 67 | ### Deprecated 68 | 69 | - Nothing. 70 | 71 | ### Removed 72 | 73 | - Nothing. 74 | 75 | ### Fixed 76 | 77 | - [#206](https://github.com/zendframework/zend-servicemanager/pull/206) fixes an 78 | issue where by callables in `Class::method` notation were not being honored 79 | under PHP 5.6. 80 | 81 | ## 3.3.0 - 2017-03-01 82 | 83 | ### Added 84 | 85 | - [#180](https://github.com/zendframework/zend-servicemanager/pull/180) adds 86 | explicit support for PSR-11 (ContainerInterface) by requiring 87 | container-interop at a minimum version of 1.2.0, and adding a requirement on 88 | psr/container 1.0. `Zend\ServiceManager\ServiceLocatorInterface` now 89 | explicitly extends the `ContainerInterface` from both projects. 90 | 91 | Factory interfaces still typehint against the container-interop variant, as 92 | changing the typehint would break backwards compatibility. Users can 93 | duck-type most of these interfaces, however, by creating callables or 94 | invokables that typehint against psr/container instead. 95 | 96 | ### Deprecated 97 | 98 | - Nothing. 99 | 100 | ### Removed 101 | 102 | - Nothing. 103 | 104 | ### Fixed 105 | 106 | - Nothing. 107 | 108 | ## 3.2.1 - 2017-02-15 109 | 110 | ### Added 111 | 112 | - [#176](https://github.com/zendframework/zend-servicemanager/pull/176) adds 113 | the options `-i` or `--ignore-unresolved` to the shipped 114 | `generate-deps-for-config-factory` command. This flag allows it to build 115 | configuration for classes resolved by the `ConfigAbstractFactory` that 116 | typehint on interfaces, which was previously unsupported. 117 | 118 | ### Deprecated 119 | 120 | - Nothing. 121 | 122 | ### Removed 123 | 124 | - Nothing. 125 | 126 | ### Fixed 127 | 128 | - [#174](https://github.com/zendframework/zend-servicemanager/pull/174) updates 129 | the `ConfigAbstractFactory` to allow the `config` service to be either an 130 | `array` or an `ArrayObject`; previously, only `array` was supported. 131 | 132 | ## 3.2.0 - 2016-12-19 133 | 134 | ### Added 135 | 136 | - [#146](https://github.com/zendframework/zend-servicemanager/pull/146) adds 137 | `Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory`, which enables a 138 | configuration-based approach to providing class dependencies when all 139 | dependencies are services known to the `ServiceManager`. Please see 140 | [the documentation](docs/book/config-abstract-factory.md) for details. 141 | - [#154](https://github.com/zendframework/zend-servicemanager/pull/154) adds 142 | `Zend\ServiceManager\Tool\ConfigDumper`, which will introspect a given class 143 | to determine dependencies, and then create configuration for 144 | `Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory`, merging it with 145 | the provided configuration file. It also adds a vendor binary, 146 | `generate-deps-for-config-factory`, for generating these from the command 147 | line. 148 | - [#154](https://github.com/zendframework/zend-servicemanager/pull/154) adds 149 | `Zend\ServiceManager\Tool\FactoryCreator`, which will introspect a given class 150 | and generate a factory for it. It also adds a vendor binary, 151 | `generate-factory-for-class`, for generating these from the command line. 152 | - [#153](https://github.com/zendframework/zend-servicemanager/pull/153) adds 153 | `Zend\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory`. This 154 | class may be used as either a mapped factory or an abstract factory, and will 155 | use reflection in order to determine which dependencies to use from the 156 | container when instantiating the requested service, with the following rules: 157 | - Scalar values are not allowed, unless they have default values associated. 158 | - Values named `$config` type-hinted against `array` will be injected with the 159 | `config` service, if present. 160 | - All other array values will be provided an empty array. 161 | - Class/interface typehints will be pulled from the container. 162 | - [#150](https://github.com/zendframework/zend-servicemanager/pull/150) adds 163 | a "cookbook" section to the documentation, with an initial document detailing 164 | the pros and cons of abstract factory usage. 165 | 166 | ### Deprecated 167 | 168 | - Nothing. 169 | 170 | ### Removed 171 | 172 | - Nothing. 173 | 174 | ### Fixed 175 | 176 | - [#106](https://github.com/zendframework/zend-servicemanager/pull/106) adds 177 | detection of multiple attempts to register the same instance or named abstract 178 | factory, using a previous instance when detected. You may still use multiple 179 | discrete instances, however. 180 | 181 | ## 3.1.2 - 2016-12-19 182 | 183 | ### Added 184 | 185 | - Nothing. 186 | 187 | ### Deprecated 188 | 189 | - Nothing. 190 | 191 | ### Removed 192 | 193 | - Nothing. 194 | 195 | ### Fixed 196 | 197 | - [#167](https://github.com/zendframework/zend-servicemanager/pull/167) fixes 198 | how exception codes are provided to ServiceNotCreatedException. Previously, 199 | the code was provided as-is. However, some PHP internal exception classes, 200 | notably PDOException, can sometimes return other values (such as strings), 201 | which can lead to fatal errors when instantiating the new exception. The 202 | patch provided casts exception codes to integers to prevent these errors. 203 | 204 | ## 3.1.1 - 2016-07-15 205 | 206 | ### Added 207 | 208 | - Nothing. 209 | 210 | ### Deprecated 211 | 212 | - Nothing. 213 | 214 | ### Removed 215 | 216 | - Nothing. 217 | 218 | ### Fixed 219 | 220 | - [#136](https://github.com/zendframework/zend-servicemanager/pull/136) removes 221 | several imports to classes in subnamespaces within the `ServiceManager` 222 | classfile, removing potential name resolution conflicts that occurred in edge 223 | cases when testing. 224 | 225 | ## 3.1.0 - 2016-06-01 226 | 227 | ### Added 228 | 229 | - [#103](https://github.com/zendframework/zend-servicemanager/pull/103) Allowing 230 | installation of `ocramius/proxy-manager` `^2.0` together with 231 | `zendframework/zend-servicemanager`. 232 | - [#103](https://github.com/zendframework/zend-servicemanager/pull/103) Disallowing 233 | test failures when running tests against PHP `7.0.*`. 234 | - [#113](https://github.com/zendframework/zend-servicemanager/pull/113) Improved performance 235 | when dealing with registering aliases and factories via `ServiceManager#setFactory()` and 236 | `ServiceManager#setAlias()` 237 | - [#120](https://github.com/zendframework/zend-servicemanager/pull/120) The 238 | `zendframework/zend-servicemanager` component now provides a 239 | `container-interop/container-interop-implementation` implementation 240 | 241 | ### Deprecated 242 | 243 | - Nothing. 244 | 245 | ### Removed 246 | 247 | - Nothing. 248 | 249 | ### Fixed 250 | 251 | - [#97](https://github.com/zendframework/zend-servicemanager/pull/97) Typo corrections 252 | in the delegator factories documentation. 253 | - [#98](https://github.com/zendframework/zend-servicemanager/pull/98) Using coveralls ^1.0 254 | for tracking test code coverage changes. 255 | 256 | ## 3.0.4 - TBD 257 | 258 | ### Added 259 | 260 | - Nothing. 261 | 262 | ### Deprecated 263 | 264 | - Nothing. 265 | 266 | ### Removed 267 | 268 | - Nothing. 269 | 270 | ### Fixed 271 | 272 | - Nothing. 273 | 274 | ## 3.0.3 - 2016-02-02 275 | 276 | ### Added 277 | 278 | - [#89](https://github.com/zendframework/zend-servicemanager/pull/89) adds 279 | cyclic alias detection to the `ServiceManager`; it now raises a 280 | `Zend\ServiceManager\Exception\CyclicAliasException` when one is detected, 281 | detailing the cycle detected. 282 | - [#95](https://github.com/zendframework/zend-servicemanager/pull/95) adds 283 | GitHub Pages publication automation, and moves the documentation to 284 | https://zendframework.github.io/zend-servicemanager/ 285 | - [#93](https://github.com/zendframework/zend-servicemanager/pull/93) adds 286 | `Zend\ServiceManager\Test\CommonPluginManagerTrait`, which can be used to 287 | validate that a plugin manager instance is ready for version 3. 288 | 289 | ### Deprecated 290 | 291 | - Nothing. 292 | 293 | ### Removed 294 | 295 | - Nothing. 296 | 297 | ### Fixed 298 | 299 | - [#90](https://github.com/zendframework/zend-servicemanager/pull/90) fixes 300 | several examples in the configuration chapter of the documentation, ensuring 301 | that the signatures are correct. 302 | - [#92](https://github.com/zendframework/zend-servicemanager/pull/92) ensures 303 | that alias resolution is skipped during configuration if no aliases are 304 | present, and forward-ports the test from [#81](https://github.com/zendframework/zend-servicemanager/pull/81) 305 | to validate v2/v3 compatibility for plugin managers. 306 | 307 | ## 3.0.2 - 2016-01-24 308 | 309 | ### Added 310 | 311 | - [#64](https://github.com/zendframework/zend-servicemanager/pull/64) performance optimizations 312 | when dealing with alias resolution during service manager instantiation 313 | 314 | ### Deprecated 315 | 316 | - Nothing. 317 | 318 | ### Removed 319 | 320 | - Nothing. 321 | 322 | ### Fixed 323 | 324 | - [#62](https://github.com/zendframework/zend-servicemanager/pull/62) 325 | [#64](https://github.com/zendframework/zend-servicemanager/pull/64) corrected benchmark assets signature 326 | - [#72](https://github.com/zendframework/zend-servicemanager/pull/72) corrected link to the Proxy Pattern Wikipedia 327 | page in the documentation 328 | - [#78](https://github.com/zendframework/zend-servicemanager/issues/78) 329 | [#79](https://github.com/zendframework/zend-servicemanager/pull/79) creation context was not being correctly passed 330 | to abstract factories when using plugin managers 331 | - [#82](https://github.com/zendframework/zend-servicemanager/pull/82) corrected migration guide in the DocBlock of 332 | the `InitializerInterface` 333 | 334 | ## 3.0.1 - 2016-01-19 335 | 336 | ### Added 337 | 338 | - Nothing. 339 | 340 | ### Deprecated 341 | 342 | - Nothing. 343 | 344 | ### Removed 345 | 346 | - [#68](https://github.com/zendframework/zend-servicemanager/pull/68) removes 347 | the dependency on zend-stdlib by inlining the `ArrayUtils::merge()` routine 348 | as a private method of `Zend\ServiceManager\Config`. 349 | 350 | ### Fixed 351 | 352 | - Nothing. 353 | 354 | ## 3.0.0 - 2016-01-11 355 | 356 | First stable release of version 3 of zend-servicemanager. 357 | 358 | Documentation is now available at http://zend-servicemanager.rtfd.org 359 | 360 | ### Added 361 | 362 | - You can now map multiple key names to the same factory. It was previously 363 | possible in ZF2 but it was not enforced by the `FactoryInterface` interface. 364 | Now the interface receives the `$requestedName` as the *second* parameter 365 | (previously, it was the third). 366 | 367 | Example: 368 | 369 | ```php 370 | $sm = new \Zend\ServiceManager\ServiceManager([ 371 | 'factories' => [ 372 | MyClassA::class => MyFactory::class, 373 | MyClassB::class => MyFactory::class, 374 | 'MyClassC' => 'MyFactory' // This is equivalent as using ::class 375 | ], 376 | ]); 377 | 378 | $sm->get(MyClassA::class); // MyFactory will receive MyClassA::class as second parameter 379 | ``` 380 | 381 | - Writing a plugin manager has been simplified. If you have simple needs, you no 382 | longer need to implement the complete `validate` method. 383 | 384 | In versions 2.x, if your plugin manager only allows creating instances that 385 | implement `Zend\Validator\ValidatorInterface`, you needed to write the 386 | following code: 387 | 388 | ```php 389 | class MyPluginManager extends AbstractPluginManager 390 | { 391 | public function validate($instance) 392 | { 393 | if ($instance instanceof \Zend\Validator\ValidatorInterface) { 394 | return; 395 | } 396 | 397 | throw new InvalidServiceException(sprintf( 398 | 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received', 399 | __CLASS__, 400 | \Zend\Validator\ValidatorInterface::class, 401 | is_object($instance) ? get_class($instance) : gettype($instance) 402 | )); 403 | } 404 | } 405 | ``` 406 | 407 | In version 3, this becomes: 408 | 409 | ```php 410 | use Zend\ServiceManager\AbstractPluginManager; 411 | use Zend\Validator\ValidatorInterface; 412 | 413 | class MyPluginManager extends AbstractPluginManager 414 | { 415 | protected $instanceOf = ValidatorInterface::class; 416 | } 417 | ``` 418 | 419 | Of course, you can still override the `validate` method if your logic is more 420 | complex. 421 | 422 | To aid migration, `validate()` will check for a `validatePlugin()` method (which 423 | was required in v2), and proxy to it if found, after emitting an 424 | `E_USER_DEPRECATED` notice prompting you to rename the method. 425 | 426 | - A new method, `configure()`, was added, allowing full configuration of the 427 | `ServiceManager` instance at once. Each of the various configuration methods — 428 | `setAlias()`, `setInvokableClass()`, etc. — now proxy to this method. 429 | 430 | - A new method, `mapLazyService($name, $class = null)`, was added, to allow 431 | mapping a lazy service, and as an analog to the other various service 432 | definition methods. 433 | 434 | ### Deprecated 435 | 436 | - Nothing 437 | 438 | ### Removed 439 | 440 | - Peering has been removed. It was a complex and rarely used feature that was 441 | misunderstood most of the time. 442 | 443 | - Integration with `Zend\Di` has been removed. It may be re-integrated later. 444 | 445 | - `MutableCreationOptionsInterface` has been removed, as options can now be 446 | passed directly through factories. 447 | 448 | - `ServiceLocatorAwareInterface` and its associated trait has been removed. It 449 | was an anti-pattern, and you are encouraged to inject your dependencies in 450 | factories instead of injecting the whole service locator. 451 | 452 | ### Changed/Fixed 453 | 454 | v3 of the ServiceManager component is a completely rewritten, more efficient 455 | implementation of the service locator pattern. It includes a number of breaking 456 | changes, outlined in this section. 457 | 458 | - You no longer need a `Zend\ServiceManager\Config` object to configure the 459 | service manager; you can pass the configuration array directly instead. 460 | 461 | In version 2.x: 462 | 463 | ```php 464 | $config = new \Zend\ServiceManager\Config([ 465 | 'factories' => [...] 466 | ]); 467 | 468 | $sm = new \Zend\ServiceManager\ServiceManager($config); 469 | ``` 470 | 471 | In ZF 3.x: 472 | 473 | ```php 474 | $sm = new \Zend\ServiceManager\ServiceManager([ 475 | 'factories' => [...] 476 | ]); 477 | ``` 478 | 479 | `Config` and `ConfigInterface` still exist, however, but primarily for the 480 | purposes of codifying and aggregating configuration to use. 481 | 482 | - `ConfigInterface` has two important changes: 483 | - `configureServiceManager()` now **must** return the updated service manager 484 | instance. 485 | - A new method, `toArray()`, was added, to allow pulling the configuration in 486 | order to pass to a ServiceManager or plugin manager's constructor or 487 | `configure()` method. 488 | 489 | - Interfaces for `FactoryInterface`, `DelegatorFactoryInterface` and 490 | `AbstractFactoryInterface` have changed. All are now directly invokable. This 491 | allows a number of performance optimization internally. 492 | 493 | Additionally, all signatures that accepted a "canonical name" argument now 494 | remove it. 495 | 496 | Most of the time, rewriting a factory to match the new interface implies 497 | replacing the method name by `__invoke`, and removing the canonical name 498 | argument if present. 499 | 500 | For instance, here is a simple version 2.x factory: 501 | 502 | ```php 503 | class MyFactory implements FactoryInterface 504 | { 505 | function createService(ServiceLocatorInterface $sl) 506 | { 507 | // ... 508 | } 509 | } 510 | ``` 511 | 512 | The equivalent version 3 factory: 513 | 514 | ```php 515 | class MyFactory implements FactoryInterface 516 | { 517 | function __invoke(ServiceLocatorInterface $sl, $requestedName) 518 | { 519 | // ... 520 | } 521 | } 522 | ``` 523 | 524 | Note another change in the above: factories also receive a second parameter, 525 | enforced through the interface, that allows you to easily map multiple service 526 | names to the same factory. 527 | 528 | To provide forwards compatibility, the original interfaces have been retained, 529 | but extend the new interfaces (which are under new namespaces). You can implement 530 | the new methods in your existing v2 factories in order to make them forwards 531 | compatible with v3. 532 | 533 | - The for `AbstractFactoryInterface` interface renames the method `canCreateServiceWithName()` 534 | to `canCreate()`, and merges the `$name` and `$requestedName` arguments. 535 | 536 | - Plugin managers will now receive the parent service locator instead of itself 537 | in factories. In version 2.x, you needed to call the method 538 | `getServiceLocator()` to retrieve the parent (application) service locator. 539 | This was confusing, and not IDE friendly as this method was not enforced 540 | through the interface. 541 | 542 | In version 2.x, if a factory was set to a service name defined in a plugin manager: 543 | 544 | ```php 545 | class MyFactory implements FactoryInterface 546 | { 547 | function createService(ServiceLocatorInterface $sl) 548 | { 549 | // $sl is actually a plugin manager 550 | 551 | $parentLocator = $sl->getServiceLocator(); 552 | 553 | // ... 554 | } 555 | } 556 | ``` 557 | 558 | In version 3: 559 | 560 | ```php 561 | class MyFactory implements FactoryInterface 562 | { 563 | function __invoke(ServiceLocatorInterface $sl, $requestedName) 564 | { 565 | // $sl is already the main, parent service locator. If you need to 566 | // retrieve the plugin manager again, you can retrieve it through the 567 | // servicelocator: 568 | $pluginManager = $sl->get(MyPluginManager::class); 569 | // ... 570 | } 571 | } 572 | ``` 573 | 574 | In practice, this should reduce code, as dependencies often come from the main 575 | service locator, and not the plugin manager itself. 576 | 577 | To assist in migration, the method `getServiceLocator()` was added to `ServiceManager` 578 | to ensure that existing factories continue to work; the method emits an `E_USER_DEPRECATED` 579 | message to signal developers to update their factories. 580 | 581 | - `PluginManager` now enforces the need for the main service locator in its 582 | constructor. In v2.x, people often forgot to set the parent locator, which led 583 | to bugs in factories trying to fetch dependencies from the parent locator. 584 | Additionally, plugin managers now pull dependencies from the parent locator by 585 | default; if you need to pull a peer plugin, your factories will now need to 586 | pull the corresponding plugin manager first. 587 | 588 | If you omit passing a service locator to the constructor, your plugin manager 589 | will continue to work, but will emit a deprecation notice indicatin you 590 | should update your initialization code. 591 | 592 | - It's so fast now that your app will fly! 593 | 594 | ## 2.7.0 - 2016-01-11 595 | 596 | ### Added 597 | 598 | - [#60](https://github.com/zendframework/zend-servicemanager/pull/60) adds 599 | forward compatibility features for `AbstractPluingManager` and introduces 600 | `InvokableFactory` to help forward migration to version 3. 601 | 602 | ### Deprecated 603 | 604 | - Nothing. 605 | 606 | ### Removed 607 | 608 | - Nothing. 609 | 610 | ### Fixed 611 | 612 | - [#46](https://github.com/zendframework/zend-servicemanager/pull/46) updates 613 | the exception hierarchy to inherit from the container-interop exceptions. 614 | This ensures that all exceptions thrown by the component follow the 615 | recommendations of that project. 616 | - [#52](https://github.com/zendframework/zend-servicemanager/pull/52) fixes 617 | the exception message thrown by `ServiceManager::setFactory()` to remove 618 | references to abstract factories. 619 | 620 | ## 2.6.0 - 2015-07-23 621 | 622 | ### Added 623 | 624 | - [#4](https://github.com/zendframework/zend-servicemanager/pull/4) updates the 625 | `ServiceManager` to [implement the container-interop interface](https://github.com/container-interop/container-interop), 626 | allowing interoperability with applications that consume that interface. 627 | 628 | ### Deprecated 629 | 630 | - Nothing. 631 | 632 | ### Removed 633 | 634 | - Nothing. 635 | 636 | ### Fixed 637 | 638 | - [#3](https://github.com/zendframework/zend-servicemanager/pull/3) properly updates the 639 | codebase to PHP 5.5, by taking advantage of the default closure binding 640 | (`$this` in a closure is the invoking object when created within a method). It 641 | also removes several `@requires PHP 5.4.0` annotations. 642 | -------------------------------------------------------------------------------- /src/ServiceManager.php: -------------------------------------------------------------------------------- 1 | [ 112 | * MyService::class => true, // will be shared, even if "sharedByDefault" is false 113 | * MyOtherService::class => false // won't be shared, even if "sharedByDefault" is true 114 | * ] 115 | * 116 | * @var boolean[] 117 | */ 118 | protected $shared = []; 119 | 120 | /** 121 | * Should the services be shared by default? 122 | * 123 | * @var bool 124 | */ 125 | protected $sharedByDefault = true; 126 | 127 | /** 128 | * Service manager was already configured? 129 | * 130 | * @var bool 131 | */ 132 | protected $configured = false; 133 | 134 | /** 135 | * Cached abstract factories from string. 136 | * 137 | * @var array 138 | */ 139 | private $cachedAbstractFactories = []; 140 | 141 | /** 142 | * Constructor. 143 | * 144 | * See {@see \Zend\ServiceManager\ServiceManager::configure()} for details 145 | * on what $config accepts. 146 | * 147 | * @param array $config 148 | */ 149 | public function __construct(array $config = []) 150 | { 151 | $this->creationContext = $this; 152 | $this->configure($config); 153 | } 154 | 155 | /** 156 | * Implemented for backwards compatibility with previous plugin managers only. 157 | * 158 | * Returns the creation context. 159 | * 160 | * @deprecated since 3.0.0. Factories using 3.0 should use the container 161 | * instance passed to the factory instead. 162 | * @return ContainerInterface 163 | */ 164 | public function getServiceLocator() 165 | { 166 | trigger_error(sprintf( 167 | 'Usage of %s is deprecated since v3.0.0; please use the container passed to the factory instead', 168 | __METHOD__ 169 | ), E_USER_DEPRECATED); 170 | return $this->creationContext; 171 | } 172 | 173 | /** 174 | * {@inheritDoc} 175 | */ 176 | public function get($name) 177 | { 178 | $requestedName = $name; 179 | 180 | // We start by checking if we have cached the requested service (this 181 | // is the fastest method). 182 | if (isset($this->services[$requestedName])) { 183 | return $this->services[$requestedName]; 184 | } 185 | 186 | $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name; 187 | 188 | // Next, if the alias should be shared, and we have cached the resolved 189 | // service, use it. 190 | if ($requestedName !== $name 191 | && (! isset($this->shared[$requestedName]) || $this->shared[$requestedName]) 192 | && isset($this->services[$name]) 193 | ) { 194 | $this->services[$requestedName] = $this->services[$name]; 195 | return $this->services[$name]; 196 | } 197 | 198 | // At this point, we need to create the instance; we use the resolved 199 | // name for that. 200 | $object = $this->doCreate($name); 201 | 202 | // Cache it for later, if it is supposed to be shared. 203 | if (($this->sharedByDefault && ! isset($this->shared[$name])) 204 | || (isset($this->shared[$name]) && $this->shared[$name]) 205 | ) { 206 | $this->services[$name] = $object; 207 | } 208 | 209 | // Also do so for aliases; this allows sharing based on service name used. 210 | if ($requestedName !== $name 211 | && (($this->sharedByDefault && ! isset($this->shared[$requestedName])) 212 | || (isset($this->shared[$requestedName]) && $this->shared[$requestedName])) 213 | ) { 214 | $this->services[$requestedName] = $object; 215 | } 216 | 217 | return $object; 218 | } 219 | 220 | /** 221 | * {@inheritDoc} 222 | */ 223 | public function build($name, array $options = null) 224 | { 225 | // We never cache when using "build" 226 | $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name; 227 | return $this->doCreate($name, $options); 228 | } 229 | 230 | /** 231 | * {@inheritDoc} 232 | */ 233 | public function has($name) 234 | { 235 | $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name; 236 | $found = isset($this->services[$name]) || isset($this->factories[$name]); 237 | 238 | if ($found) { 239 | return $found; 240 | } 241 | 242 | // Check abstract factories 243 | foreach ($this->abstractFactories as $abstractFactory) { 244 | if ($abstractFactory->canCreate($this->creationContext, $name)) { 245 | return true; 246 | } 247 | } 248 | 249 | return false; 250 | } 251 | 252 | /** 253 | * Indicate whether or not the instance is immutable. 254 | * 255 | * @param bool $flag 256 | */ 257 | public function setAllowOverride($flag) 258 | { 259 | $this->allowOverride = (bool) $flag; 260 | } 261 | 262 | /** 263 | * Retrieve the flag indicating immutability status. 264 | * 265 | * @return bool 266 | */ 267 | public function getAllowOverride() 268 | { 269 | return $this->allowOverride; 270 | } 271 | 272 | /** 273 | * Configure the service manager 274 | * 275 | * Valid top keys are: 276 | * 277 | * - services: service name => service instance pairs 278 | * - invokables: service name => class name pairs for classes that do not 279 | * have required constructor arguments; internally, maps the class to an 280 | * InvokableFactory instance, and creates an alias if the service name 281 | * and class name do not match. 282 | * - factories: service name => factory pairs; factories may be any 283 | * callable, string name resolving to an invokable class, or string name 284 | * resolving to a FactoryInterface instance. 285 | * - abstract_factories: an array of abstract factories; these may be 286 | * instances of AbstractFactoryInterface, or string names resolving to 287 | * classes that implement that interface. 288 | * - delegators: service name => list of delegator factories for the given 289 | * service; each item in the list may be a callable, a string name 290 | * resolving to an invokable class, or a string name resolving to a class 291 | * implementing DelegatorFactoryInterface. 292 | * - shared: service name => flag pairs; the flag is a boolean indicating 293 | * whether or not the service is shared. 294 | * - aliases: alias => service name pairs. 295 | * - lazy_services: lazy service configuration; can contain the keys: 296 | * - class_map: service name => class name pairs. 297 | * - proxies_namespace: string namespace to use for generated proxy 298 | * classes. 299 | * - proxies_target_dir: directory in which to write generated proxy 300 | * classes; uses system temporary by default. 301 | * - write_proxy_files: boolean indicating whether generated proxy 302 | * classes should be written; defaults to boolean false. 303 | * - shared_by_default: boolean, indicating if services in this instance 304 | * should be shared by default. 305 | * 306 | * @param array $config 307 | * @return self 308 | * @throws ContainerModificationsNotAllowedException if the allow 309 | * override flag has been toggled off, and a service instance 310 | * exists for a given service. 311 | */ 312 | public function configure(array $config) 313 | { 314 | $this->validateOverrides($config); 315 | 316 | if (isset($config['services'])) { 317 | $this->services = $config['services'] + $this->services; 318 | } 319 | 320 | if (isset($config['invokables']) && ! empty($config['invokables'])) { 321 | $aliases = $this->createAliasesForInvokables($config['invokables']); 322 | $factories = $this->createFactoriesForInvokables($config['invokables']); 323 | 324 | if (! empty($aliases)) { 325 | $config['aliases'] = (isset($config['aliases'])) 326 | ? array_merge($config['aliases'], $aliases) 327 | : $aliases; 328 | } 329 | 330 | $config['factories'] = (isset($config['factories'])) 331 | ? array_merge($config['factories'], $factories) 332 | : $factories; 333 | } 334 | 335 | if (isset($config['factories'])) { 336 | $this->factories = $config['factories'] + $this->factories; 337 | } 338 | 339 | if (isset($config['delegators'])) { 340 | $this->delegators = array_merge_recursive($this->delegators, $config['delegators']); 341 | } 342 | 343 | if (isset($config['shared'])) { 344 | $this->shared = $config['shared'] + $this->shared; 345 | } 346 | 347 | if (isset($config['aliases'])) { 348 | $this->configureAliases($config['aliases']); 349 | } elseif (! $this->configured && ! empty($this->aliases)) { 350 | $this->resolveAliases($this->aliases); 351 | } 352 | 353 | if (isset($config['shared_by_default'])) { 354 | $this->sharedByDefault = $config['shared_by_default']; 355 | } 356 | 357 | // If lazy service configuration was provided, reset the lazy services 358 | // delegator factory. 359 | if (isset($config['lazy_services']) && ! empty($config['lazy_services'])) { 360 | $this->lazyServices = array_merge_recursive($this->lazyServices, $config['lazy_services']); 361 | $this->lazyServicesDelegator = null; 362 | } 363 | 364 | // For abstract factories and initializers, we always directly 365 | // instantiate them to avoid checks during service construction. 366 | if (isset($config['abstract_factories'])) { 367 | $this->resolveAbstractFactories($config['abstract_factories']); 368 | } 369 | 370 | if (isset($config['initializers'])) { 371 | $this->resolveInitializers($config['initializers']); 372 | } 373 | 374 | $this->configured = true; 375 | 376 | return $this; 377 | } 378 | 379 | /** 380 | * @param string[] $aliases 381 | * 382 | * @return void 383 | */ 384 | private function configureAliases(array $aliases) 385 | { 386 | if (! $this->configured) { 387 | $this->aliases = $aliases + $this->aliases; 388 | 389 | $this->resolveAliases($this->aliases); 390 | 391 | return; 392 | } 393 | 394 | // Performance optimization. If there are no collisions, then we don't need to recompute loops 395 | $intersecting = $this->aliases && \array_intersect_key($this->aliases, $aliases); 396 | $this->aliases = $this->aliases ? \array_merge($this->aliases, $aliases) : $aliases; 397 | 398 | if ($intersecting) { 399 | $this->resolveAliases($this->aliases); 400 | 401 | return; 402 | } 403 | 404 | $this->resolveAliases($aliases); 405 | $this->resolveNewAliasesWithPreviouslyResolvedAliases($aliases); 406 | } 407 | 408 | /** 409 | * Add an alias. 410 | * 411 | * @param string $alias 412 | * @param string $target 413 | */ 414 | public function setAlias($alias, $target) 415 | { 416 | $this->configure(['aliases' => [$alias => $target]]); 417 | } 418 | 419 | /** 420 | * Add an invokable class mapping. 421 | * 422 | * @param string $name Service name 423 | * @param null|string $class Class to which to map; if omitted, $name is 424 | * assumed. 425 | */ 426 | public function setInvokableClass($name, $class = null) 427 | { 428 | $this->configure(['invokables' => [$name => $class ?: $name]]); 429 | } 430 | 431 | /** 432 | * Specify a factory for a given service name. 433 | * 434 | * @param string $name Service name 435 | * @param string|callable|Factory\FactoryInterface $factory Factory to which 436 | * to map. 437 | */ 438 | public function setFactory($name, $factory) 439 | { 440 | $this->configure(['factories' => [$name => $factory]]); 441 | } 442 | 443 | /** 444 | * Create a lazy service mapping to a class. 445 | * 446 | * @param string $name Service name to map 447 | * @param null|string $class Class to which to map; if not provided, $name 448 | * will be used for the mapping. 449 | */ 450 | public function mapLazyService($name, $class = null) 451 | { 452 | $this->configure(['lazy_services' => ['class_map' => [$name => $class ?: $name]]]); 453 | } 454 | 455 | /** 456 | * Add an abstract factory for resolving services. 457 | * 458 | * @param string|Factory\AbstractFactoryInterface $factory Service name 459 | */ 460 | public function addAbstractFactory($factory) 461 | { 462 | $this->configure(['abstract_factories' => [$factory]]); 463 | } 464 | 465 | /** 466 | * Add a delegator for a given service. 467 | * 468 | * @param string $name Service name 469 | * @param string|callable|Factory\DelegatorFactoryInterface $factory Delegator 470 | * factory to assign. 471 | */ 472 | public function addDelegator($name, $factory) 473 | { 474 | $this->configure(['delegators' => [$name => [$factory]]]); 475 | } 476 | 477 | /** 478 | * Add an initializer. 479 | * 480 | * @param string|callable|Initializer\InitializerInterface $initializer 481 | */ 482 | public function addInitializer($initializer) 483 | { 484 | $this->configure(['initializers' => [$initializer]]); 485 | } 486 | 487 | /** 488 | * Map a service. 489 | * 490 | * @param string $name Service name 491 | * @param array|object $service 492 | */ 493 | public function setService($name, $service) 494 | { 495 | $this->configure(['services' => [$name => $service]]); 496 | } 497 | 498 | /** 499 | * Add a service sharing rule. 500 | * 501 | * @param string $name Service name 502 | * @param boolean $flag Whether or not the service should be shared. 503 | */ 504 | public function setShared($name, $flag) 505 | { 506 | $this->configure(['shared' => [$name => (bool) $flag]]); 507 | } 508 | 509 | /** 510 | * Instantiate abstract factories for to avoid checks during service construction. 511 | * 512 | * @param string[]|Factory\AbstractFactoryInterface[] $abstractFactories 513 | * 514 | * @return void 515 | */ 516 | private function resolveAbstractFactories(array $abstractFactories) 517 | { 518 | foreach ($abstractFactories as $abstractFactory) { 519 | if (is_string($abstractFactory) && class_exists($abstractFactory)) { 520 | //Cached string 521 | if (! isset($this->cachedAbstractFactories[$abstractFactory])) { 522 | $this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory(); 523 | } 524 | 525 | $abstractFactory = $this->cachedAbstractFactories[$abstractFactory]; 526 | } 527 | 528 | if ($abstractFactory instanceof Factory\AbstractFactoryInterface) { 529 | $abstractFactoryObjHash = spl_object_hash($abstractFactory); 530 | $this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory; 531 | continue; 532 | } 533 | 534 | // Error condition; let's find out why. 535 | 536 | // If we still have a string, we have a class name that does not resolve 537 | if (is_string($abstractFactory)) { 538 | throw new InvalidArgumentException( 539 | sprintf( 540 | 'An invalid abstract factory was registered; resolved to class "%s" ' . 541 | 'which does not exist; please provide a valid class name resolving ' . 542 | 'to an implementation of %s', 543 | $abstractFactory, 544 | AbstractFactoryInterface::class 545 | ) 546 | ); 547 | } 548 | 549 | // Otherwise, we have an invalid type. 550 | throw new InvalidArgumentException( 551 | sprintf( 552 | 'An invalid abstract factory was registered. Expected an instance of "%s", ' . 553 | 'but "%s" was received', 554 | AbstractFactoryInterface::class, 555 | (is_object($abstractFactory) ? get_class($abstractFactory) : gettype($abstractFactory)) 556 | ) 557 | ); 558 | } 559 | } 560 | 561 | /** 562 | * Instantiate initializers for to avoid checks during service construction. 563 | * 564 | * @param string[]|Initializer\InitializerInterface[]|callable[] $initializers 565 | * 566 | * @return void 567 | */ 568 | private function resolveInitializers(array $initializers) 569 | { 570 | foreach ($initializers as $initializer) { 571 | if (is_string($initializer) && class_exists($initializer)) { 572 | $initializer = new $initializer(); 573 | } 574 | 575 | if (is_callable($initializer)) { 576 | $this->initializers[] = $initializer; 577 | continue; 578 | } 579 | 580 | // Error condition; let's find out why. 581 | 582 | if (is_string($initializer)) { 583 | throw new InvalidArgumentException( 584 | sprintf( 585 | 'An invalid initializer was registered; resolved to class or function "%s" ' . 586 | 'which does not exist; please provide a valid function name or class ' . 587 | 'name resolving to an implementation of %s', 588 | $initializer, 589 | Initializer\InitializerInterface::class 590 | ) 591 | ); 592 | } 593 | 594 | // Otherwise, we have an invalid type. 595 | throw new InvalidArgumentException( 596 | sprintf( 597 | 'An invalid initializer was registered. Expected a callable, or an instance of ' . 598 | '(or string class name resolving to) "%s", ' . 599 | 'but "%s" was received', 600 | Initializer\InitializerInterface::class, 601 | (is_object($initializer) ? get_class($initializer) : gettype($initializer)) 602 | ) 603 | ); 604 | } 605 | } 606 | 607 | /** 608 | * Resolve aliases to their canonical service names. 609 | * 610 | * @param string[] $aliases 611 | * 612 | * @return void 613 | */ 614 | private function resolveAliases(array $aliases) 615 | { 616 | foreach ($aliases as $alias => $service) { 617 | $visited = []; 618 | $name = $alias; 619 | 620 | while (isset($this->aliases[$name])) { 621 | if (isset($visited[$name])) { 622 | throw CyclicAliasException::fromAliasesMap($aliases); 623 | } 624 | 625 | $visited[$name] = true; 626 | $name = $this->aliases[$name]; 627 | } 628 | 629 | $this->resolvedAliases[$alias] = $name; 630 | } 631 | } 632 | 633 | /** 634 | * Rewrites the map of aliases by resolving the given $aliases with the existing resolved ones. 635 | * This is mostly done for performance reasons. 636 | * 637 | * @param string[] $aliases 638 | * 639 | * @return void 640 | */ 641 | private function resolveNewAliasesWithPreviouslyResolvedAliases(array $aliases) 642 | { 643 | foreach ($this->resolvedAliases as $name => $target) { 644 | if (isset($aliases[$target])) { 645 | $this->resolvedAliases[$name] = $this->resolvedAliases[$target]; 646 | } 647 | } 648 | } 649 | 650 | /** 651 | * Get a factory for the given service name 652 | * 653 | * @param string $name 654 | * @return callable 655 | * @throws ServiceNotFoundException 656 | */ 657 | private function getFactory($name) 658 | { 659 | $factory = isset($this->factories[$name]) ? $this->factories[$name] : null; 660 | 661 | $lazyLoaded = false; 662 | if (is_string($factory) && class_exists($factory)) { 663 | $factory = new $factory(); 664 | $lazyLoaded = true; 665 | } 666 | 667 | if (is_callable($factory)) { 668 | if ($lazyLoaded) { 669 | $this->factories[$name] = $factory; 670 | } 671 | // PHP 5.6 fails on 'class::method' callables unless we explode them: 672 | if (PHP_MAJOR_VERSION < 7 673 | && is_string($factory) && strpos($factory, '::') !== false 674 | ) { 675 | $factory = explode('::', $factory); 676 | } 677 | return $factory; 678 | } 679 | 680 | // Check abstract factories 681 | foreach ($this->abstractFactories as $abstractFactory) { 682 | if ($abstractFactory->canCreate($this->creationContext, $name)) { 683 | return $abstractFactory; 684 | } 685 | } 686 | 687 | throw new ServiceNotFoundException(sprintf( 688 | 'Unable to resolve service "%s" to a factory; are you certain you provided it during configuration?', 689 | $name 690 | )); 691 | } 692 | 693 | /** 694 | * @param string $name 695 | * @param null|array $options 696 | * @return object 697 | */ 698 | private function createDelegatorFromName($name, array $options = null) 699 | { 700 | $creationCallback = function () use ($name, $options) { 701 | // Code is inlined for performance reason, instead of abstracting the creation 702 | $factory = $this->getFactory($name); 703 | return $factory($this->creationContext, $name, $options); 704 | }; 705 | 706 | foreach ($this->delegators[$name] as $index => $delegatorFactory) { 707 | $delegatorFactory = $this->delegators[$name][$index]; 708 | 709 | if ($delegatorFactory === Proxy\LazyServiceFactory::class) { 710 | $delegatorFactory = $this->createLazyServiceDelegatorFactory(); 711 | } 712 | 713 | if (is_string($delegatorFactory) && class_exists($delegatorFactory)) { 714 | $delegatorFactory = new $delegatorFactory(); 715 | } 716 | 717 | if (! is_callable($delegatorFactory)) { 718 | if (is_string($delegatorFactory)) { 719 | throw new ServiceNotCreatedException(sprintf( 720 | 'An invalid delegator factory was registered; resolved to class or function "%s" ' 721 | . 'which does not exist; please provide a valid function name or class name resolving ' 722 | . 'to an implementation of %s', 723 | $delegatorFactory, 724 | DelegatorFactoryInterface::class 725 | )); 726 | } 727 | 728 | throw new ServiceNotCreatedException(sprintf( 729 | 'A non-callable delegator, "%s", was provided; expected a callable or instance of "%s"', 730 | is_object($delegatorFactory) ? get_class($delegatorFactory) : gettype($delegatorFactory), 731 | DelegatorFactoryInterface::class 732 | )); 733 | } 734 | 735 | $this->delegators[$name][$index] = $delegatorFactory; 736 | 737 | $creationCallback = function () use ($delegatorFactory, $name, $creationCallback, $options) { 738 | return $delegatorFactory($this->creationContext, $name, $creationCallback, $options); 739 | }; 740 | } 741 | 742 | return $creationCallback($this->creationContext, $name, $creationCallback, $options); 743 | } 744 | 745 | /** 746 | * Create a new instance with an already resolved name 747 | * 748 | * This is a highly performance sensitive method, do not modify if you have not benchmarked it carefully 749 | * 750 | * @param string $resolvedName 751 | * @param null|array $options 752 | * @return mixed 753 | * @throws ServiceNotFoundException if unable to resolve the service. 754 | * @throws ServiceNotCreatedException if an exception is raised when 755 | * creating a service. 756 | * @throws ContainerException if any other error occurs 757 | */ 758 | private function doCreate($resolvedName, array $options = null) 759 | { 760 | try { 761 | if (! isset($this->delegators[$resolvedName])) { 762 | // Let's create the service by fetching the factory 763 | $factory = $this->getFactory($resolvedName); 764 | $object = $factory($this->creationContext, $resolvedName, $options); 765 | } else { 766 | $object = $this->createDelegatorFromName($resolvedName, $options); 767 | } 768 | } catch (ContainerException $exception) { 769 | throw $exception; 770 | } catch (Exception $exception) { 771 | throw new ServiceNotCreatedException(sprintf( 772 | 'Service with name "%s" could not be created. Reason: %s', 773 | $resolvedName, 774 | $exception->getMessage() 775 | ), (int) $exception->getCode(), $exception); 776 | } 777 | 778 | foreach ($this->initializers as $initializer) { 779 | $initializer($this->creationContext, $object); 780 | } 781 | 782 | return $object; 783 | } 784 | 785 | /** 786 | * Create the lazy services delegator factory. 787 | * 788 | * Creates the lazy services delegator factory based on the lazy_services 789 | * configuration present. 790 | * 791 | * @return Proxy\LazyServiceFactory 792 | * @throws ServiceNotCreatedException when the lazy service class_map 793 | * configuration is missing 794 | */ 795 | private function createLazyServiceDelegatorFactory() 796 | { 797 | if ($this->lazyServicesDelegator) { 798 | return $this->lazyServicesDelegator; 799 | } 800 | 801 | if (! isset($this->lazyServices['class_map'])) { 802 | throw new ServiceNotCreatedException('Missing "class_map" config key in "lazy_services"'); 803 | } 804 | 805 | $factoryConfig = new ProxyConfiguration(); 806 | 807 | if (isset($this->lazyServices['proxies_namespace'])) { 808 | $factoryConfig->setProxiesNamespace($this->lazyServices['proxies_namespace']); 809 | } 810 | 811 | if (isset($this->lazyServices['proxies_target_dir'])) { 812 | $factoryConfig->setProxiesTargetDir($this->lazyServices['proxies_target_dir']); 813 | } 814 | 815 | if (! isset($this->lazyServices['write_proxy_files']) || ! $this->lazyServices['write_proxy_files']) { 816 | $factoryConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); 817 | } else { 818 | $factoryConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy( 819 | new FileLocator($factoryConfig->getProxiesTargetDir()) 820 | )); 821 | } 822 | 823 | spl_autoload_register($factoryConfig->getProxyAutoloader()); 824 | 825 | $this->lazyServicesDelegator = new Proxy\LazyServiceFactory( 826 | new LazyLoadingValueHolderFactory($factoryConfig), 827 | $this->lazyServices['class_map'] 828 | ); 829 | 830 | return $this->lazyServicesDelegator; 831 | } 832 | 833 | /** 834 | * Create aliases for invokable classes. 835 | * 836 | * If an invokable service name does not match the class it maps to, this 837 | * creates an alias to the class (which will later be mapped as an 838 | * invokable factory). 839 | * 840 | * @param array $invokables 841 | * @return array 842 | */ 843 | private function createAliasesForInvokables(array $invokables) 844 | { 845 | $aliases = []; 846 | foreach ($invokables as $name => $class) { 847 | if ($name === $class) { 848 | continue; 849 | } 850 | $aliases[$name] = $class; 851 | } 852 | return $aliases; 853 | } 854 | 855 | /** 856 | * Create invokable factories for invokable classes. 857 | * 858 | * If an invokable service name does not match the class it maps to, this 859 | * creates an invokable factory entry for the class name; otherwise, it 860 | * creates an invokable factory for the entry name. 861 | * 862 | * @param array $invokables 863 | * @return array 864 | */ 865 | private function createFactoriesForInvokables(array $invokables) 866 | { 867 | $factories = []; 868 | foreach ($invokables as $name => $class) { 869 | if ($name === $class) { 870 | $factories[$name] = Factory\InvokableFactory::class; 871 | continue; 872 | } 873 | 874 | $factories[$class] = Factory\InvokableFactory::class; 875 | } 876 | return $factories; 877 | } 878 | 879 | /** 880 | * Determine if one or more services already exist in the container. 881 | * 882 | * If the allow override flag is true or it's first time configured, 883 | * this method does nothing. 884 | * 885 | * Otherwise, it checks against each of the following service types, 886 | * if present, and validates that none are defining services that 887 | * already exist; if they do, it raises an exception indicating 888 | * modification is not allowed. 889 | * 890 | * @param array $config 891 | * @throws ContainerModificationsNotAllowedException if any services 892 | * provided already have instances available. 893 | */ 894 | private function validateOverrides(array $config) 895 | { 896 | if ($this->allowOverride || ! $this->configured) { 897 | return; 898 | } 899 | 900 | if (isset($config['services'])) { 901 | $this->validateOverrideSet(array_keys($config['services']), 'service'); 902 | } 903 | 904 | if (isset($config['aliases'])) { 905 | $this->validateOverrideSet(array_keys($config['aliases']), 'alias'); 906 | } 907 | 908 | if (isset($config['invokables'])) { 909 | $this->validateOverrideSet(array_keys($config['invokables']), 'invokable class'); 910 | } 911 | 912 | if (isset($config['factories'])) { 913 | $this->validateOverrideSet(array_keys($config['factories']), 'factory'); 914 | } 915 | 916 | if (isset($config['delegators'])) { 917 | $this->validateOverrideSet(array_keys($config['delegators']), 'delegator'); 918 | } 919 | 920 | if (isset($config['shared'])) { 921 | $this->validateOverrideSet(array_keys($config['shared']), 'sharing rule'); 922 | } 923 | 924 | if (isset($config['lazy_services']['class_map'])) { 925 | $this->validateOverrideSet(array_keys($config['lazy_services']['class_map']), 'lazy service'); 926 | } 927 | } 928 | 929 | /** 930 | * Determine if one or more services already exist for a given type. 931 | * 932 | * Loops through the provided service names, checking if any have current 933 | * service instances; if not, it returns, but otherwise, it raises an 934 | * exception indicating modification is not allowed. 935 | * 936 | * @param string[] $services 937 | * @param string $type Type of service being checked. 938 | * @throws ContainerModificationsNotAllowedException if any services 939 | * provided already have instances available. 940 | */ 941 | private function validateOverrideSet(array $services, $type) 942 | { 943 | $detected = []; 944 | foreach ($services as $service) { 945 | if (isset($this->services[$service])) { 946 | $detected[] = $service; 947 | } 948 | } 949 | 950 | if (empty($detected)) { 951 | return; 952 | } 953 | 954 | throw new ContainerModificationsNotAllowedException(sprintf( 955 | 'An updated/new %s is not allowed, as the container does not allow ' 956 | . 'changes for services with existing instances; the following ' 957 | . 'already exist in the container: %s', 958 | $type, 959 | implode(', ', $detected) 960 | )); 961 | } 962 | } 963 | --------------------------------------------------------------------------------