validateClassName($className); $reflectionClass = new ReflectionClass($className); // class is an interface; do nothing if ($reflectionClass->isInterface()) { return $config; } // class has no constructor, treat it as an invokable if (! $reflectionClass->getConstructor()) { return $this->createInvokable($config, $className); } $constructorArguments = $reflectionClass->getConstructor()->getParameters(); $constructorArguments = array_filter( $constructorArguments, static fn(ReflectionParameter $argument): bool => ! $argument->isOptional() ); // has no required parameters, treat it as an invokable if (empty($constructorArguments)) { return $this->createInvokable($config, $className); } $classConfig = []; foreach ($constructorArguments as $constructorArgument) { $type = $constructorArgument->getType(); $argumentType = $type instanceof ReflectionNamedType && ! $type->isBuiltin() ? $type->getName() : null; if ($argumentType === null) { if ($ignoreUnresolved) { // don't throw an exception, just return the previous config return $config; } // don't throw an exception if the class is an already defined service if ($this->container && $this->container->has($className)) { return $config; } throw new InvalidArgumentException(sprintf( 'Cannot create config for constructor argument "%s", ' . 'it has no type hint, or non-class/interface type hint', $constructorArgument->getName() )); } $config = $this->createDependencyConfig($config, $argumentType, $ignoreUnresolved); $classConfig[] = $argumentType; } $config[ConfigAbstractFactory::class][$className] = $classConfig; return $config; } /** * @param string $className * @throws InvalidArgumentException If class name is not a string or does * not exist. */ private function validateClassName($className) { if (! is_string($className)) { throw new InvalidArgumentException('Class name must be a string, ' . gettype($className) . ' given'); } if (! class_exists($className) && ! interface_exists($className)) { throw new InvalidArgumentException('Cannot find class or interface with name ' . $className); } } /** * @param string $className * @return array */ private function createInvokable(array $config, $className) { $config[ConfigAbstractFactory::class][$className] = []; return $config; } /** * @return array * @throws InvalidArgumentException If ConfigAbstractFactory configuration * value is not an array. */ public function createFactoryMappingsFromConfig(array $config) { if (! array_key_exists(ConfigAbstractFactory::class, $config)) { return $config; } if (! is_array($config[ConfigAbstractFactory::class])) { throw new InvalidArgumentException( 'Config key for ' . ConfigAbstractFactory::class . ' should be an array, ' . gettype( $config[ConfigAbstractFactory::class] ) . ' given' ); } foreach ($config[ConfigAbstractFactory::class] as $className => $dependency) { $config = $this->createFactoryMappings($config, $className); } return $config; } /** * @param string $className * @return array */ public function createFactoryMappings(array $config, $className) { $this->validateClassName($className); if ( array_key_exists('service_manager', $config) && array_key_exists('factories', $config['service_manager']) && array_key_exists($className, $config['service_manager']['factories']) ) { return $config; } $config['service_manager']['factories'][$className] = ConfigAbstractFactory::class; return $config; } /** * @return string */ public function dumpConfigFile(array $config) { $prepared = $this->prepareConfig($config); return sprintf( self::CONFIG_TEMPLATE, static::class, date('Y-m-d H:i:s'), $prepared ); } /** * @param array|Traversable $config * @param int $indentLevel * @return string */ private function prepareConfig($config, $indentLevel = 1) { $indent = str_repeat(' ', $indentLevel * 4); $entries = []; foreach ($config as $key => $value) { $key = $this->createConfigKey($key); $entries[] = sprintf( '%s%s%s,', $indent, $key ? sprintf('%s => ', $key) : '', $this->createConfigValue($value, $indentLevel) ); } $outerIndent = str_repeat(' ', ($indentLevel - 1) * 4); return sprintf( "[\n%s\n%s]", implode("\n", $entries), $outerIndent ); } /** * @param string|int|null $key * @return null|string */ private function createConfigKey($key) { if (is_string($key) && class_exists($key)) { return sprintf('\\%s::class', $key); } if (is_int($key)) { return null; } return sprintf("'%s'", $key); } /** * @param int $indentLevel * @return string */ private function createConfigValue(mixed $value, $indentLevel) { if (is_array($value) || $value instanceof Traversable) { return $this->prepareConfig($value, $indentLevel + 1); } if (is_string($value) && class_exists($value)) { return sprintf('\\%s::class', $value); } return var_export($value, true); } }