Skip to content

Commit 2e9de12

Browse files
committed
Simplifying bundle extension/config definition
1 parent 9eec2db commit 2e9de12

File tree

8 files changed

+213
-3
lines changed

8 files changed

+213
-3
lines changed

Bundle/AbstractBundle.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\Configurator\DefinitionConfigurator;
15+
use Symfony\Component\DependencyInjection\Container;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Extension\ConfigurableExtensionInterface;
18+
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
19+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
20+
21+
/**
22+
* A Bundle that provides configuration hooks.
23+
*
24+
* @author Yonel Ceruto <yonelceruto@gmail.com>
25+
*/
26+
abstract class AbstractBundle extends Bundle implements ConfigurableExtensionInterface
27+
{
28+
protected string $extensionAlias = '';
29+
30+
public function configure(DefinitionConfigurator $definition): void
31+
{
32+
}
33+
34+
public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
35+
{
36+
}
37+
38+
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
39+
{
40+
}
41+
42+
public function getContainerExtension(): ?ExtensionInterface
43+
{
44+
if ('' === $this->extensionAlias) {
45+
$this->extensionAlias = Container::underscore(preg_replace('/Bundle$/', '', $this->getName()));
46+
}
47+
48+
return $this->extension ??= new BundleExtension($this, $this->extensionAlias);
49+
}
50+
}

Bundle/BundleExtension.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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\Configuration;
15+
use Symfony\Component\Config\Definition\ConfigurationInterface;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Extension\ConfigurableExtensionInterface;
18+
use Symfony\Component\DependencyInjection\Extension\Extension;
19+
use Symfony\Component\DependencyInjection\Extension\ExtensionTrait;
20+
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
21+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
22+
23+
/**
24+
* @author Yonel Ceruto <yonelceruto@gmail.com>
25+
*
26+
* @internal
27+
*/
28+
class BundleExtension extends Extension implements PrependExtensionInterface
29+
{
30+
use ExtensionTrait;
31+
32+
public function __construct(
33+
private ConfigurableExtensionInterface $subject,
34+
private string $alias,
35+
) {
36+
}
37+
38+
public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface
39+
{
40+
return new Configuration($this->subject, $container, $this->getAlias());
41+
}
42+
43+
public function getAlias(): string
44+
{
45+
return $this->alias;
46+
}
47+
48+
public function prepend(ContainerBuilder $container): void
49+
{
50+
$callback = function (ContainerConfigurator $configurator) use ($container) {
51+
$this->subject->prependExtension($configurator, $container);
52+
};
53+
54+
$this->executeConfiguratorCallback($container, $callback, $this->subject);
55+
}
56+
57+
public function load(array $configs, ContainerBuilder $container): void
58+
{
59+
$config = $this->processConfiguration($this->getConfiguration([], $container), $configs);
60+
61+
$callback = function (ContainerConfigurator $configurator) use ($config, $container) {
62+
$this->subject->loadExtension($config, $configurator, $container);
63+
};
64+
65+
$this->executeConfiguratorCallback($container, $callback, $this->subject);
66+
}
67+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Deprecate StreamedResponseListener, it's not needed anymore
1010
* Add `Profiler::isEnabled()` so collaborating collector services may elect to omit themselves.
1111
* Add the `UidValueResolver` argument value resolver
12+
* Add `AbstractBundle` class for DI configuration/definition on a single file
1213

1314
6.0
1415
---

Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
1617
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
1718
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
19+
use Symfony\Component\HttpKernel\Tests\Fixtures\AcmeFooBundle\AcmeFooBundle;
1820

1921
class MergeExtensionConfigurationPassTest extends TestCase
2022
{
@@ -31,6 +33,27 @@ public function testAutoloadMainExtension()
3133
$this->assertTrue($container->hasDefinition('loaded.foo'));
3234
$this->assertTrue($container->hasDefinition('not_loaded.bar'));
3335
}
36+
37+
public function testFooBundle()
38+
{
39+
$bundle = new AcmeFooBundle();
40+
41+
$container = new ContainerBuilder(new ParameterBag([
42+
'kernel.environment' => 'test',
43+
'kernel.build_dir' => sys_get_temp_dir(),
44+
]));
45+
$container->registerExtension(new LoadedExtension());
46+
$container->registerExtension($bundle->getContainerExtension());
47+
48+
$configPass = new MergeExtensionConfigurationPass(['loaded', 'acme_foo']);
49+
$configPass->process($container);
50+
51+
$this->assertSame([[], ['bar' => 'baz']], $container->getExtensionConfig('loaded'), '->prependExtension() prepends an extension config');
52+
$this->assertTrue($container->hasDefinition('acme_foo.foo'), '->loadExtension() registers a service');
53+
$this->assertTrue($container->hasDefinition('acme_foo.bar'), '->loadExtension() imports a service');
54+
$this->assertTrue($container->hasParameter('acme_foo.config'), '->loadExtension() sets a parameter');
55+
$this->assertSame(['foo' => 'bar', 'ping' => 'pong'], $container->getParameter('acme_foo.config'), '->loadConfiguration() defines and loads configurations');
56+
}
3457
}
3558

3659
class LoadedExtension extends Extension
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\Tests\Fixtures\AcmeFooBundle;
13+
14+
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
17+
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
18+
19+
class AcmeFooBundle extends AbstractBundle
20+
{
21+
public function configure(DefinitionConfigurator $definition): void
22+
{
23+
$definition->rootNode()
24+
->children()
25+
->scalarNode('foo')->defaultValue('bar')->end()
26+
->end()
27+
;
28+
29+
$definition->import('Resources/config/definition.php');
30+
}
31+
32+
public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
33+
{
34+
$container->extension('loaded', ['bar' => 'baz']);
35+
}
36+
37+
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
38+
{
39+
$container->parameters()
40+
->set('acme_foo.config', $config)
41+
;
42+
43+
$container->services()
44+
->set('acme_foo.foo', \stdClass::class)
45+
;
46+
47+
$container->import('Resources/config/services.php');
48+
}
49+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
4+
5+
return static function (DefinitionConfigurator $definition) {
6+
$definition->rootNode()
7+
->children()
8+
->scalarNode('ping')->defaultValue('pong')->end()
9+
->end()
10+
;
11+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
4+
5+
return static function (ContainerConfigurator $container) {
6+
$container->services()
7+
->set('acme_foo.bar', \stdClass::class)
8+
;
9+
};

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"require-dev": {
2727
"symfony/browser-kit": "^5.4|^6.0",
28-
"symfony/config": "^5.4|^6.0",
28+
"symfony/config": "^6.1",
2929
"symfony/console": "^5.4|^6.0",
3030
"symfony/css-selector": "^5.4|^6.0",
3131
"symfony/dependency-injection": "^6.1",
@@ -48,10 +48,10 @@
4848
"conflict": {
4949
"symfony/browser-kit": "<5.4",
5050
"symfony/cache": "<5.4",
51-
"symfony/config": "<5.4",
51+
"symfony/config": "<6.1",
5252
"symfony/console": "<5.4",
5353
"symfony/form": "<5.4",
54-
"symfony/dependency-injection": "<5.4",
54+
"symfony/dependency-injection": "<6.1",
5555
"symfony/doctrine-bridge": "<5.4",
5656
"symfony/http-client": "<5.4",
5757
"symfony/mailer": "<5.4",

0 commit comments

Comments
 (0)