8000 Simplifying DI extension/config definition · symfony/symfony@571cb70 · GitHub
[go: up one dir, main page]

Skip to content

Commit 571cb70

Browse files
committed
Simplifying DI extension/config definition
1 parent ff70bd1 commit 571cb70

File tree

8 files changed

+340
-7
lines changed

8 files changed

+340
-7
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition\Loader;
13+
14+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
15+
use Symfony\Component\Config\FileLocatorInterface;
16+
use Symfony\Component\Config\Loader\FileLoader;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
19+
20+
class PhpFileLoader extends FileLoader
21+
{
22+
private $treeBuilder;
23+
private $container;
24+
25+
public function __construct(TreeBuilder $treeBuilder, ContainerBuilder $container, FileLocatorInterface $locator, string $env = null)
26+
{
27+
$this->treeBuilder = $treeBuilder;
28+
$this->container = $container;
29+
30+
parent::__construct($locator, $env);
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function load($resource, string $type = null)
37+
{
38+
// the container and loader variables are exposed to the included file below
39+
$parameters = $this->container->getParameterBag();
40+
$loader = $this;
41+
42+
$path = $this->locator->locate($resource);
43+
$this->setCurrentDir(\dirname($path));
44+
$this->container->fileExists($path);
45+
46+
// the closure forbids access to the private scope in the included file
47+
$load = \Closure::bind(static function ($file) use ($parameters, $loader) {
48+
return include $file;
49+
}, null, ProtectedPhpFileLoader::class);
50+
51+
$callback = $load($path);
52+
53+
if (\is_object($callback) && \is_callable($callback)) {
54+
$this->executeCallback($callback, $path);
55+
}
56+
57+
return null;
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function supports($resource, string $type = null): bool
64+
{
65+
if (!\is_string($resource)) {
66+
return false;
67+
}
68+
69+
if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) {
70+
return true;
71+
}
72+
73+
return 'php' === $type;
74+
}
75+
76+
private function executeCallback(callable $callback, string $path): void
77+
{
78+
if (!$callback instanceof \Closure) {
79+
$callback = \Closure::fromCallable($callback);
80+
}
81+
82+
$arguments = [];
83+
$r = new \ReflectionFunction($callback);
84+
85+
foreach ($r->getParameters() as $parameter) {
86+
$reflectionType = $parameter->getType();
87+
if (!$reflectionType instanceof \ReflectionNamedType) {
88+
throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s").', $parameter->getName(), $path, TreeBuilder::class));
89+
}
90+
$type = $reflectionType->getName();
91+
92+
switch ($type) {
93+
case TreeBuilder::class:
94+
$arguments[] = $this->treeBuilder;
95+
break;
96+
case ParameterBagInterface::class:
97+
$arguments[] = $this->container->getParameterBag();
98+
break;
99+
case FileLoader::class:
100+
case self::class:
101+
$arguments[] = $this;
102+
break;
103+
}
104+
}
105+
106+
$callback(...$arguments);
107+
}
108+
}
109+
110+
/**
111+
* @internal
112+
*/
113+
final class ProtectedPhpFileLoader extends PhpFileLoader
114+
{
115+
}

src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,24 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\Config\Definition\BaseNode;
15+
use Symfony\Component\Config\FileLocator;
16+
use Symfony\Component\Config\Loader\DelegatingLoader;
17+
use Symfony\Component\Config\Loader\LoaderResolver;
18+
use Symfony\Component\DependencyInjection\ContainerInterface;
19+
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionProcessorInterface;
1520
use Symfony\Component\DependencyInjection\ContainerBuilder;
1621
use Symfony\Component\DependencyInjection\Exception\LogicException;
1722
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1823
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
19-
use Symfony\Component\DependencyInjection\Extension\Extension;
2024
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2125
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
26+
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
27+
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
28+
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
29+
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
30+
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
31+
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
32+
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2233
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
2334
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
2435

@@ -52,7 +63,7 @@ public function process(ContainerBuilder $container)
5263
continue;
5364
}
5465
$resolvingBag = $container->getParameterBag();
55-
if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) {
66+
if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof ConfigurationExtensionProcessorInterface) {
5667
// create a dedicated bag so that we can track env vars per-extension
5768
$resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag);
5869
if ($configAvailable) {
@@ -73,7 +84,7 @@ public function process(ContainerBuilder $container)
7384
$tmpContainer->addExpressionLanguageProvider($provider);
7485
}
7586

76-
$extension->load($config, $tmpContainer);
87+
$extension->load($config, $tmpContainer, $this->getContainerLoader($tmpContainer));
7788
} catch (\Exception $e) {
7889
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
7990
$container->getParameterBag()->mergeEnvPlaceholders($resolvingBag);
@@ -102,6 +113,23 @@ public function process(ContainerBuilder $container)
102113
$container->addDefinitions($definitions);
103114
$container->addAliases($aliases);
104115
}
116+
117+
private function getContainerLoader(ContainerInterface $container): DelegatingLoader
118+
{
119+
$env = $container->hasParameter('kernel.environment') ? $container->getParameter('kernel.environment') : null;
120+
$locator = new FileLocator();
121+
$resolver = new LoaderResolver([
122+
new XmlFileLoader($container, $locator, $env),
123+
new YamlFileLoader($container, $locator, $env),
124+
new IniFileLoader($container, $locator, $env),
125+
new PhpFileLoader($container, $locator, $env),
126+
new GlobFileLoader($container, $locator, $env),
127+
new DirectoryLoader($container, $locator, $env),
128+
new ClosureLoader($container, $env),
129+
]);
130+
131+
return new DelegatingLoader($resolver);
132+
}
105133
}
106134

107135
/**
@@ -117,7 +145,7 @@ public function __construct(parent $parameterBag)
117145
$this->mergeEnvPlaceholders($parameterBag);
118146
}
119147

120-
public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container)
148+
public function freezeAfterProcessing(ConfigurationExtensionProcessorInterface $extension, ContainerBuilder $container)
121149
{
122150
if (!$config = $extension->getProcessedConfigs()) {
123151
// Extension::processConfiguration() wasn't called, we cannot know how configs were merged
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Extension;
13+
14+
use Symfony\Component\Config\Definition\ConfigurationInterface;
15+
16+
/**
17+
* @author Yonel Ceruto <yonelceruto@gmail.com>
18+
*/
19+
interface ConfigurationExtensionProcessorInterface
20+
{
21+
public function processConfiguration(ConfigurationInterface $configuration, array $configs): array;
22+
23+
public function getProcessedConfigs(): array;
24+
}

src/Symfony/Component/DependencyInjection/Extension/Extension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*
2525
* @author Fabien Potencier <fabien@symfony.com>
2626
*/
27-
abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface
27+
abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface, ConfigurationExtensionProcessorInterface
2828
{
2929
private $processedConfigs = [];
3030

@@ -104,7 +104,7 @@ public function getConfiguration(array $config, ContainerBuilder $container)
104104
return null;
105105
}
106106

107-
final protected function processConfiguration(ConfigurationInterface $configuration, array $configs): array
107+
final public function processConfiguration(ConfigurationInterface $configuration, array $configs): array
108108
{
109109
$processor = new Processor();
110110

src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Extension;
1313

14+
use Symfony\Component\Config\Loader\LoaderInterface;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516

1617
/**
@@ -25,7 +26,7 @@ interface ExtensionInterface
2526
*
2627
* @throws \InvalidArgumentException When provided tag is not defined in this extension
2728
*/
28-
public function load(array $configs, ContainerBuilder $container);
29+
public function load(array $configs, ContainerBuilder $container/*, LoaderInterface $loader*/);
2930

3031
/**
3132
* Returns the namespace to be used for this extension (XML namespace).
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Bundle;
13+
14+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
15+
use Symfony\Component\Config\Loader\LoaderInterface;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
18+
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionProcessorInterface;
19+
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
20+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
21+
22+
/**
23+
* @author Yonel Ceruto <yonelceruto@gmail.com>
24+
*/
25+
interface MicroBundleInterface extends BundleInterface, ExtensionInterface, ConfigurationExtensionInterface, ConfigurationExtensionProcessorInterface
26+
{
27+
public function configureConfig(TreeBuilder $builder, LoaderInterface $loader): void;
28+
29+
public function configureContainer(array $mergedConfig, ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder): void;
30+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Bundle;
13+
14+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
15+
use Symfony\Component\Config\Definition\ConfigurationInterface;
16+
use Symfony\Component\Config\Definition\Processor;
17+
use Symfony\Component\Config\Loader\LoaderInterface;
18+
use Symfony\Component\DependencyInjection\Container;
19+
use Symfony\Component\DependencyInjection\ContainerBuilder;
20+
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
21+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
22+
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
23+
24+
trait MicroBundleTrait
25+
{
26+
private $configureContainerClosure;
27+
private $processedConfigs = [];
28+
29+
protected function createContainerExtension(): ?ExtensionInterface
30+
{
31+
return $this;
32+
}
33+
34+
public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface
35+
{
36+
return new MicroConfiguration($this, $container);
37+
}
38+
39+
public function getAlias(): string
40+
{
41+
return Container::underscore(preg_replace('/Bundle$/', '', $this->getName()));
42+
}
43+
44+
public function getXsdValidationBasePath(): bool
45+
{
46+
return false;
47+
}
48+
49+
final public function processConfiguration(ConfigurationInterface $configuration, array $configs): array
50+
{
51+
$processor = new Processor();
52+
53+
return $this->processedConfigs[] = $processor->processConfiguration($configuration, $configs);
54+
}
55+
56+
/**
57+
* @internal
58+
*/
59+
final public function getProcessedConfigs(): array
60+
{
61+
try {
62+
return $this->processedConfigs;
63+
} finally {
64+
$this->processedConfigs = [];
65+
}
66+
}
67+
68+
public function load(array $configs, ContainerBuilder $container, LoaderInterface $loader = null): void
69+
{
70+
if (null === $loader) {
71+
return;
72+
}
73+
74+
$mergedConfig = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);
75+
76+
$file = (new \ReflectionObject($this))->getFileName();
77+
/* @var PhpFileLoader $bundleLoader */
78+
$bundleLoader = $loader->getResolver()->resolve($file);
79+ $bundleLoader->setCurrentDir(\dirname($file));
80+
$instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $bundleLoader, $bundleLoader)();
81+
$env = \Closure::bind(function () { return $this->env; }, $bundleLoader, $bundleLoader)();
82+
83+
try {
84+
$this->configureContainer($mergedConfig, new ContainerConfigurator($container, $bundleLoader, $instanceof, $file, $file, $env), $loader, $container);
85+
} finally {
86+
$instanceof = [];
87+
$bundleLoader->registerAliasesForSinglyImplementedInterfaces();
88+
}
89+
}
90+
91+
public function configureConfig(TreeBuilder $builder, LoaderInterface $loader): void
92+
{
93+
}
94+
}

0 commit comments

Comments
 (0)
0