From 874ae04636e10fadef97d6c4da1beecf92a17260 Mon Sep 17 00:00:00 2001 From: Maxime STEINHAUSSER Date: Wed, 17 Aug 2016 11:17:14 +0200 Subject: [PATCH 1/3] [POC] [Dependency Injection] Introduce the contextualized container builder --- .../MergeExtensionConfigurationPass.php | 2 +- .../DependencyInjection/ContainerBuilder.php | 30 ++++- .../Component/DependencyInjection/Context.php | 116 ++++++++++++++++++ ...ontextualizedContainerBuilderInterface.php | 25 ++++ .../ContextElementNotFoundException.php | 40 ++++++ 5 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Context.php create mode 100644 src/Symfony/Component/DependencyInjection/ContextualizedContainerBuilderInterface.php create mode 100644 src/Symfony/Component/DependencyInjection/Exception/ContextElementNotFoundException.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php index f9e6024164c15..1ea77ad86c95e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php @@ -44,7 +44,7 @@ public function process(ContainerBuilder $container) } $config = $container->getParameterBag()->resolveValue($config); - $tmpContainer = new ContainerBuilder($container->getParameterBag()); + $tmpContainer = new ContainerBuilder($container->getParameterBag(), $container->getContext()); $tmpContainer->setResourceTracking($container->isTrackingResources()); $tmpContainer->addObjectResource($extension); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index cb3789165891a..b3b0958b66f57 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -25,6 +25,7 @@ use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -33,7 +34,7 @@ * * @author Fabien Potencier */ -class ContainerBuilder extends Container implements TaggedContainerInterface +class ContainerBuilder extends Container implements TaggedContainerInterface, ContextualizedContainerBuilderInterface { /** * @var ExtensionInterface[] @@ -89,8 +90,21 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $usedTags = array(); + private $context; + private $compiled = false; + /** + * @param ParameterBagInterface|null $parameterBag A ParameterBagInterface instance + * @param Context|null $context A Context instance, or null + */ + public function __construct(ParameterBagInterface $parameterBag = null, Context $context = null) + { + parent::__construct($parameterBag); + + $this->context = $context ?: new Context(); + } + /** * Sets the track resources flag. * @@ -468,6 +482,10 @@ public function merge(ContainerBuilder $container) $this->addAliases($container->getAliases()); $this->getParameterBag()->add($container->getParameterBag()->all()); + if (!$this->context->isLocked()) { + $this->context->merge($container->getContext()); + } + if ($this->trackResources) { foreach ($container->getResources() as $resource) { $this->addResource($resource); @@ -538,6 +556,8 @@ public function compile() } } + $this->getContext()->lock(); + $compiler->compile($this); $this->compiled = true; @@ -1017,6 +1037,14 @@ public static function getServiceConditionals($value) return $services; } + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + /** * Retrieves the currently set proxy instantiator or instantiates one. * diff --git a/src/Symfony/Component/DependencyInjection/Context.php b/src/Symfony/Component/DependencyInjection/Context.php new file mode 100644 index 0000000000000..41f4eba792cc2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Context.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; +use Symfony\Component\DependencyInjection\Exception\ContextElementNotFoundException; + +/** + * @author Maxime Steinhausser + */ +final class Context +{ + private $elements; + private $locked = false; + + /** + * Creates a new context allowing to share elements during the container building phase. + * + * Context elements is an array where the values are the elements to share + * and the keys are strings allowing to retrieve an element. + * + * For instance: + * + * new Context(array('kernel' => new BootingKernel($kernel))); + * + * @param array $elements An array of elements indexed by a string. + */ + public function __construct(array $elements = array()) + { + $this->elements = $elements; + } + + /** + * @param string $key + * + * @return bool + */ + public function has($key) + { + return isset($this->elements[$key]); + } + + /** + * @param string $key + * + * @return mixed + * + * @throws ContextElementNotFoundException When no element exists for this key. + */ + public function get($key) + { + if (!$this->has($key)) { + throw new ContextElementNotFoundException($key); + } + + return $this->elements[$key]; + } + + /** + * @param string $key + * @param mixed $element + * + * @throws BadMethodCallException When trying to set an element on a locked context. + */ + public function set($key, $element) + { + if ($this->isLocked()) { + throw new BadMethodCallException(sprintf('Setting element "%s" on a locked context is not allowed.', $key)); + } + + $this->elements[$key] = $element; + } + + /** + * Locks the context making it immutable. + */ + public function lock() + { + $this->locked = true; + } + + /** + * @return bool + */ + public function isLocked() + { + return $this->locked; + } + + /** + * Merges elements found in the given context into the current one. + * + * @param Context $context + * + * @throws BadMethodCallException When trying to merge into a locked context. + */ + public function merge(Context $context) + { + if ($this->isLocked()) { + throw new BadMethodCallException('Cannot merge on a locked context.'); + } + + foreach ($context->elements as $key => $element) { + $this->set($key, $element); + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/ContextualizedContainerBuilderInterface.php b/src/Symfony/Component/DependencyInjection/ContextualizedContainerBuilderInterface.php new file mode 100644 index 0000000000000..88976ec898aeb --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/ContextualizedContainerBuilderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Implemented by a ContainerBuilder sharing a context during build time. + * + * @author Maxime Steinhausser + */ +interface ContextualizedContainerBuilderInterface extends ContainerInterface +{ + /** + * @return Context + */ + public function getContext(); +} diff --git a/src/Symfony/Component/DependencyInjection/Exception/ContextElementNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ContextElementNotFoundException.php new file mode 100644 index 0000000000000..704a77bafc21a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Exception/ContextElementNotFoundException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a non-existent context element is used. + * + * @author Maxime Steinhausser + */ +class ContextElementNotFoundException extends InvalidArgumentException +{ + private $key; + + /** + * @param string $key + */ + public function __construct($key) + { + $this->key = $key; + + parent::__construct(sprintf('Context element not found for "%s" key', $key)); + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } +} From 17f0277136490db2dcf8f82cd1bad2b7f5473c60 Mon Sep 17 00:00:00 2001 From: Maxime STEINHAUSSER Date: Fri, 19 Aug 2016 11:00:24 +0200 Subject: [PATCH 2/3] [Dependency Injection] Make the Context immutable --- .../DependencyInjection/ContainerBuilder.php | 8 +-- .../Component/DependencyInjection/Context.php | 70 ++++--------------- 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index b3b0958b66f57..7199784588dc5 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -102,7 +102,7 @@ public function __construct(ParameterBagInterface $parameterBag = null, Context { parent::__construct($parameterBag); - $this->context = $context ?: new Context(); + $this->context = $context ?: Context::create(); } /** @@ -482,10 +482,6 @@ public function merge(ContainerBuilder $container) $this->addAliases($container->getAliases()); $this->getParameterBag()->add($container->getParameterBag()->all()); - if (!$this->context->isLocked()) { - $this->context->merge($container->getContext()); - } - if ($this->trackResources) { foreach ($container->getResources() as $resource) { $this->addResource($resource); @@ -556,8 +552,6 @@ public function compile() } } - $this->getContext()->lock(); - $compiler->compile($this); $this->compiled = true; diff --git a/src/Symfony/Component/DependencyInjection/Context.php b/src/Symfony/Component/DependencyInjection/Context.php index 41f4eba792cc2..9fad7793af441 100644 --- a/src/Symfony/Component/DependencyInjection/Context.php +++ b/src/Symfony/Component/DependencyInjection/Context.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection; -use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\ContextElementNotFoundException; /** @@ -20,7 +19,11 @@ final class Context { private $elements; - private $locked = false; + + private function __construct(array $elements = array()) + { + $this->elements = $elements; + } /** * Creates a new context allowing to share elements during the container building phase. @@ -30,13 +33,19 @@ final class Context * * For instance: * - * new Context(array('kernel' => new BootingKernel($kernel))); + * Context::create(array('kernel' => new BootingKernel($kernel))); + * + * You can optionally pass an existing Context instance. Thus, original elements are reused, + * but new context elements replace any existing key. * - * @param array $elements An array of elements indexed by a string. + * @param array $elements An array of elements indexed by a string. + * @param Context|null $previous An optional Context instance used as base. + * + * @return Context */ - public function __construct(array $elements = array()) + public static function create(array $elements = array(), Context $previous = null) { - $this->elements = $elements; + return new self($previous ? array_replace($previous->elements, $elements) : $elements); } /** @@ -64,53 +73,4 @@ public function get($key) return $this->elements[$key]; } - - /** - * @param string $key - * @param mixed $element - * - * @throws BadMethodCallException When trying to set an element on a locked context. - */ - public function set($key, $element) - { - if ($this->isLocked()) { - throw new BadMethodCallException(sprintf('Setting element "%s" on a locked context is not allowed.', $key)); - } - - $this->elements[$key] = $element; - } - - /** - * Locks the context making it immutable. - */ - public function lock() - { - $this->locked = true; - } - - /** - * @return bool - */ - public function isLocked() - { - return $this->locked; - } - - /** - * Merges elements found in the given context into the current one. - * - * @param Context $context - * - * @throws BadMethodCallException When trying to merge into a locked context. - */ - public function merge(Context $context) - { - if ($this->isLocked()) { - throw new BadMethodCallException('Cannot merge on a locked context.'); - } - - foreach ($context->elements as $key => $element) { - $this->set($key, $element); - } - } } From ccae9bdd929ad5b6332c69848d1fafcf017f857e Mon Sep 17 00:00:00 2001 From: Maxime STEINHAUSSER Date: Wed, 17 Aug 2016 11:18:24 +0200 Subject: [PATCH 3/3] [POC] [HttpKernel] Set a BootingKernel instance into ContainerBuilder's context --- .../Component/HttpKernel/BootingKernel.php | 191 ++++++++++++++++++ .../HttpKernel/Bundle/BootingBundle.php | 101 +++++++++ src/Symfony/Component/HttpKernel/Kernel.php | 3 +- .../Component/HttpKernel/composer.json | 2 +- 4 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/BootingKernel.php create mode 100644 src/Symfony/Component/HttpKernel/Bundle/BootingBundle.php diff --git a/src/Symfony/Component/HttpKernel/BootingKernel.php b/src/Symfony/Component/HttpKernel/BootingKernel.php new file mode 100644 index 0000000000000..e7d34b1c74740 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/BootingKernel.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Bundle\BootingBundle; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; + +/** + * @author Maxime Steinhausser + */ +class BootingKernel implements KernelInterface +{ + private $innerKernel; + + public function __construct(KernelInterface $innerKernel) + { + $this->innerKernel = $innerKernel; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return $this->innerKernel->serialize(); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $this->innerKernel->unserialize($serialized); + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function registerBundles() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function registerContainerConfiguration(LoaderInterface $loader) + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function boot() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function shutdown() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function getBundles() + { + return array_map(function (BundleInterface $bundle) { + return new BootingBundle($bundle); + }, $this->innerKernel->getBundles()); + } + + /** + * {@inheritdoc} + */ + public function getBundle($name, $first = true) + { + if (is_array($bundle = $this->innerKernel->getBundle($name, $first))) { + return array_map(function (BundleInterface $bundle) { + return new BootingBundle($bundle); + }, $bundle); + } + + return new BootingBundle($bundle); + } + + /** + * {@inheritdoc} + */ + public function locateResource($name, $dir = null, $first = true) + { + return $this->innerKernel->locateResource($name, $dir, $first); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->innerKernel->getName(); + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() + { + return $this->innerKernel->getEnvironment(); + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->innerKernel->isDebug(); + } + + /** + * {@inheritdoc} + */ + public function getRootDir() + { + return $this->innerKernel->getRootDir(); + } + + /** + * {@inheritdoc} + */ + public function getContainer() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function getStartTime() + { + return $this->innerKernel->getStartTime(); + } + + /** + * {@inheritdoc} + */ + public function getCacheDir() + { + return $this->innerKernel->getCacheDir(); + } + + /** + * {@inheritdoc} + */ + public function getLogDir() + { + return $this->innerKernel->getLogDir(); + } + + /** + * {@inheritdoc} + */ + public function getCharset() + { + return $this->innerKernel->getCharset(); + } +} diff --git a/src/Symfony/Component/HttpKernel/Bundle/BootingBundle.php b/src/Symfony/Component/HttpKernel/Bundle/BootingBundle.php new file mode 100644 index 0000000000000..c46cab5ef9a2b --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Bundle/BootingBundle.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; + +/** + * @author Maxime Steinhausser + */ +class BootingBundle implements BundleInterface +{ + private $innerBundle; + + public function __construct(BundleInterface $innerBundle) + { + $this->innerBundle = $innerBundle; + } + + /** + * {@inheritdoc} + */ + public function boot() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function shutdown() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function build(ContainerBuilder $container) + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function getContainerExtension() + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return $this->innerBundle->getParent(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->innerBundle->getName(); + } + + /** + * {@inheritdoc} + */ + public function getNamespace() + { + return $this->innerBundle->getNamespace(); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->innerBundle->getPath(); + } + + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = null) + { + throw new BadMethodCallException(sprintf('Calling "%s()" is not allowed.', __METHOD__)); + } +} diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 125da81d5ca4d..18edbb66a6fa3 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -15,6 +15,7 @@ use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Context; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -611,7 +612,7 @@ protected function prepareContainer(ContainerBuilder $container) */ protected function getContainerBuilder() { - $container = new ContainerBuilder(new ParameterBag($this->getKernelParameters())); + $container = new ContainerBuilder(new ParameterBag($this->getKernelParameters()), Context::create(array('kernel' => new BootingKernel($this)))); if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { $container->setProxyInstantiator(new RuntimeInstantiator()); diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index e583b43adc167..a417170c7b3fd 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -28,7 +28,7 @@ "symfony/config": "~2.8|~3.0", "symfony/console": "~2.8|~3.0", "symfony/css-selector": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dependency-injection": "3.2.x-dev", "symfony/dom-crawler": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0",