registry = $registry; $this->registryServiceName = $registryServiceName; } /** * You can modify the container here before it is dumped to PHP code. * * @param ContainerBuilder $container */ public function process(ContainerBuilder $container): void { // Now, let's store the registry in the container (an empty version of it... it has to be added dynamically at runtime): $this->registerRegistry($container); foreach ($this->registry as $serviceProviderKey => $serviceProvider) { $this->registerFactories($container, $serviceProviderKey); } foreach ($this->registry as $serviceProviderKey => $serviceProvider) { $this->registerExtensions($container, $serviceProviderKey); } } /** * @param ContainerBuilder $container */ private function registerRegistry(ContainerBuilder $container): void { $definition = new Definition(ServiceProviderRegistry::class); $definition->setSynthetic(true); $definition->setPublic(true); $container->setDefinition($this->registryServiceName, $definition); } /** * @param ContainerBuilder $container */ private function registerFactories(ContainerBuilder $container, string $serviceProviderKey): void { $serviceFactories = $this->registry->getFactories($serviceProviderKey); foreach ($serviceFactories as $serviceName => $callable) { $this->registerService($container, $serviceName, $serviceProviderKey, $callable); } } /** * @param ContainerBuilder $container */ private function registerExtensions(ContainerBuilder $container, string $serviceProviderKey): void { $serviceExtensions = $this->registry->getExtensions($serviceProviderKey); foreach ($serviceExtensions as $serviceName => $callable) { $this->extendService($container, $serviceName, $serviceProviderKey, $callable); } } /** * @param ContainerBuilder $container */ private function registerService( ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable ): void { if (!$container->hasDefinition($serviceName)) { // Create a new definition $factoryDefinition = new Definition(); $container->setDefinition($serviceName, $factoryDefinition); } else { // Merge into an existing definition to keep possible addMethodCall/properties configurations // (which act like a service extension) // Retrieve the existing factory and overwrite it. $factoryDefinition = $container->getDefinition($serviceName); if ($factoryDefinition->isAutowired()) { $factoryDefinition->setAutowired(false); } } $className = $this->getReturnType($this->getReflection($callable), $serviceName) ?? 'object'; $factoryDefinition->setClass($className); $factoryDefinition->setPublic(true); $staticallyCallable = $this->getStaticallyCallable($callable); if ($staticallyCallable !== null) { $factoryDefinition->setFactory($staticallyCallable); $factoryDefinition->setArguments([ new Reference('service_container'), ]); } else { $factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'createService' ]); $factoryDefinition->setArguments([ $serviceProviderKey, $serviceName, new Reference('service_container'), ]); } } /** * @param ContainerBuilder $container */ private function extendService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable): void { $finalServiceName = $serviceName; $innerName = null; $reflection = $this->getReflection($callable); $previousClass = $container->hasDefinition($serviceName) ? $container->getDefinition($serviceName)->getClass() : null; $className = $this->getReturnType($reflection, $serviceName) ?? $previousClass ?? 'object'; $factoryDefinition = new Definition($className); $factoryDefinition->setClass($className); $factoryDefinition->setPublic(true); if ($container->has($serviceName)) { [$finalServiceName, $previousServiceName] = $this->getDecoratedServiceName($container, $serviceName); $innerName = $finalServiceName . '.inner'; $factoryDefinition->setDecoratedService($previousServiceName, $innerName); } elseif ($reflection->getNumberOfRequiredParameters() > 1) { throw new \Exception('A registered extension for the service "' . $serviceName . '" requires the service to be available, which is missing.', 1550092654); } $staticallyCallable = $this->getStaticallyCallable($callable); if ($staticallyCallable !== null) { $factoryDefinition->setFactory($staticallyCallable); $factoryDefinition->setArguments([ new Reference('service_container'), ]); } else { $factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'extendService' ]); $factoryDefinition->setArguments([ $serviceProviderKey, $serviceName, new Reference('service_container'), ]); } if ($innerName !== null) { $factoryDefinition->addArgument(new Reference($innerName)); } $container->setDefinition($finalServiceName, $factoryDefinition); } private function getStaticallyCallable(callable $callable): ?callable { if (is_string($callable)) { return $callable; } if (is_array($callable) && is_string($callable[0])) { return $callable; } if ($callable instanceof \Closure) { $reflection = new \ReflectionFunction($callable); $reflectionScope = $reflection->getClosureScopeClass(); $usedVariables = count($reflection->getClosureUsedVariables()); if ($reflection->isStatic() && !$reflection->isAnonymous() && $reflectionScope !== null && $usedVariables === 0) { return [ $reflectionScope->getName(), $reflection->getName(), ]; } } return null; } private function getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName): ?string { if ($reflection->getReturnType() instanceof \ReflectionNamedType) { return $reflection->getReturnType()->getName(); } if (class_exists($serviceName, true) || interface_exists($serviceName, true)) { return $serviceName; } return null; } private function getReflection(callable $callable): \ReflectionFunctionAbstract { if (is_array($callable) && count($callable) === 2) { return new \ReflectionMethod($callable[0], $callable[1]); } if (is_object($callable) && !$callable instanceof \Closure) { return new \ReflectionMethod($callable, '__invoke'); } return new \ReflectionFunction($callable); } /** * @param ContainerBuilder $container */ private function getDecoratedServiceName(ContainerBuilder $container, string $serviceName): array { $counter = 1; while ($container->has($serviceName . '_decorated_' . $counter)) { $counter++; } return [ $serviceName . '_decorated_' . $counter, $counter === 1 ? $serviceName : $serviceName . '_decorated_' . ($counter - 1), ]; } }