Skip to content

Commit 5bd0edc

Browse files
author
Yann Eugoné
committed
Merge pull request #7 from yann-eugone/feature/enum-loading-conventions
Added ability to load all enums within a single compiler pass
2 parents 38de8a5 + e610891 commit 5bd0edc

27 files changed

+595
-16
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace EnumBundle\DependencyInjection\CompilerPass;
4+
5+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
7+
8+
/**
9+
* @author Yann Eugoné <[email protected]>
10+
*/
11+
class ConventionedEnumCollectorCompilerPass implements CompilerPassInterface
12+
{
13+
/**
14+
* @var array
15+
*/
16+
private $bundles;
17+
18+
/**
19+
* @param array $bundles
20+
*/
21+
public function __construct(array $bundles)
22+
{
23+
$this->bundles = $bundles;
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function process(ContainerBuilder $container)
30+
{
31+
foreach ($this->bundles as $bundleClass) {
32+
$declarativePass = new DeclarativeEnumCollectorCompilerPass($bundleClass);
33+
$declarativePass->process($container);
34+
}
35+
}
36+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
namespace EnumBundle\DependencyInjection\CompilerPass;
4+
5+
use EnumBundle\Enum\AbstractTranslatedEnum;
6+
use EnumBundle\Enum\EnumInterface;
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\Container;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
use Symfony\Component\DependencyInjection\Definition;
11+
use Symfony\Component\DependencyInjection\DefinitionDecorator;
12+
use Symfony\Component\Finder\Finder;
13+
use Symfony\Component\Finder\SplFileInfo;
14+
15+
/**
16+
* @author Yann Eugoné <[email protected]>
17+
*/
18+
class DeclarativeEnumCollectorCompilerPass implements CompilerPassInterface
19+
{
20+
/**
21+
* @var string
22+
*/
23+
private $bundleDir;
24+
25+
/**
26+
* @var string
27+
*/
28+
private $bundleNamespace;
29+
30+
/**
31+
* @var string
32+
*/
33+
private $bundleName;
34+
35+
/**
36+
* @var string
37+
*/
38+
private $transDomain;
39+
40+
/**
41+
* @param string $bundle
42+
* @param string|null $transDomain
43+
*/
44+
public function __construct($bundle, $transDomain = null)
45+
{
46+
$reflection = new \ReflectionClass($bundle);
47+
$this->bundleDir = dirname($reflection->getFileName());
48+
$this->bundleNamespace = $reflection->getNamespaceName();
49+
$this->bundleName = $reflection->getShortName();
50+
51+
$this->transDomain = $transDomain;
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function process(ContainerBuilder $container)
58+
{
59+
if (!class_exists('Symfony\Component\Finder\Finder')) {
60+
throw new \RuntimeException('You need the symfony/finder component to register enums.');
61+
}
62+
63+
$enumDir = $this->bundleDir . '/Enum';
64+
65+
if (!is_dir($enumDir)) {
66+
return;
67+
}
68+
69+
$finder = new Finder();
70+
$finder->files()->name('*Enum.php')->in($enumDir);
71+
72+
foreach ($finder as $file) {
73+
/** @var SplFileInfo $file */
74+
$enumNamespace = $this->bundleNamespace . '\\Enum';
75+
if ($relativePath = $file->getRelativePath()) {
76+
$enumNamespace .= '\\' . strtr($relativePath, '/', '\\');
77+
}
78+
79+
$enumClass = $enumNamespace . '\\' . $file->getBasename('.php');
80+
$enumReflection = new \ReflectionClass($enumClass);
81+
82+
if (!$enumReflection->isSubclassOf(EnumInterface::class) || $enumReflection->isAbstract()) {
83+
continue; //Not an enum or abstract enum
84+
}
85+
86+
$definition = null;
87+
$requiredParameters = $enumReflection->getConstructor() ? $enumReflection->getConstructor()->getNumberOfRequiredParameters() : 0;
88+
if ($requiredParameters === 0) {
89+
$definition = new Definition($enumClass);
90+
} elseif ($requiredParameters === 2 && $enumReflection->isSubclassOf(AbstractTranslatedEnum::class)) {
91+
$definition = new DefinitionDecorator('enum.abstract_translated');
92+
$definition->setClass($enumClass);
93+
$definition->addArgument(
94+
$this->getTransPattern($enumClass)
95+
);
96+
97+
if ($this->transDomain) {
98+
$definition->addMethodCall(
99+
'setTransDomain',
100+
[
101+
$this->transDomain
102+
]
103+
);
104+
}
105+
}
106+
107+
if (!$definition) {
108+
continue; //Could not determine how to create definition for the enum
109+
}
110+
111+
$definition->addTag('enum');
112+
113+
$container->setDefinition(
114+
$this->getServiceId($enumClass),
115+
$definition
116+
);
117+
}
118+
}
119+
120+
/**
121+
* @param string $enumClass
122+
*
123+
* @return string
124+
*/
125+
private function getServiceId($enumClass)
126+
{
127+
$enumNamespace = $this->bundleNamespace.'\\Enum\\';
128+
129+
return sprintf('%s.enum.%s',
130+
Container::underscore(
131+
substr($this->bundleName, 0, -6)
132+
),
133+
Container::underscore(
134+
str_replace(
135+
'\\',
136+
'',
137+
str_replace(
138+
$enumNamespace,
139+
'',
140+
substr($enumClass, 0, -4)
141+
)
142+
)
143+
)
144+
);
145+
}
146+
147+
/**
148+
* @param string $enumClass
149+
*
150+
* @return string
151+
*/
152+
private function getTransPattern($enumClass)
153+
{
154+
$parts = array_filter(
155+
array_map(
156+
[$this, 'underscore'],
157+
explode(
158+
'\\',
159+
str_replace(
160+
$this->bundleNamespace . '\\',
161+
'',
162+
$enumClass
163+
)
164+
)
165+
)
166+
);
167+
168+
$enum = array_pop($parts);
169+
170+
return implode('_', $parts) . '.' . $enum . '.label_%s';
171+
}
172+
173+
/**
174+
* @param string $input
175+
*
176+
* @return string
177+
*/
178+
private function underscore($input)
179+
{
180+
return strtolower(
181+
preg_replace(
182+
'~(?<=\\w)([A-Z])~', '_$1',
183+
preg_replace(
184+
'~(Enum|Bundle)~',
185+
'',
186+
$input
187+
)
188+
)
189+
);
190+
}
191+
}

DependencyInjection/CompilerPass/CollectEnumCompilerPass.php renamed to DependencyInjection/CompilerPass/TaggedEnumCollectorCompilerPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* @author Yann Eugoné <[email protected]>
1111
*/
12-
class CollectEnumCompilerPass implements CompilerPassInterface
12+
class TaggedEnumCollectorCompilerPass implements CompilerPassInterface
1313
{
1414
/**
1515
* {@inheritdoc}

DependencyInjection/Configuration.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace EnumBundle\DependencyInjection;
4+
5+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
6+
use Symfony\Component\Config\Definition\ConfigurationInterface;
7+
8+
/**
9+
* @author Yann Eugoné <[email protected]>
10+
*/
11+
class Configuration implements ConfigurationInterface
12+
{
13+
/**
14+
* @inheritDoc
15+
*/
16+
public function getConfigTreeBuilder()
17+
{
18+
$treeBuilder = new TreeBuilder();
19+
$rootNode = $treeBuilder->root('debug');
20+
21+
$rootNode
22+
->children()
23+
->variableNode('register_bundles')
24+
->defaultFalse()
25+
->end()
26+
->end()
27+
;
28+
29+
return $treeBuilder;
30+
}
31+
}

DependencyInjection/EnumExtension.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ class EnumExtension extends Extension
1515
/**
1616
* {@inheritdoc}
1717
*/
18-
public function load(array $config, ContainerBuilder $container)
18+
public function load(array $configs, ContainerBuilder $container)
1919
{
20+
$configuration = new Configuration();
21+
$config = $this->processConfiguration($configuration, $configs);
22+
23+
$container->setParameter('enum.register_bundles', $config['register_bundles']);
24+
2025
$xmlLoader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
2126
$xmlLoader->load('services.xml');
2227
$xmlLoader->load('form_types.xml');

EnumBundle.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
namespace EnumBundle;
44

5-
use EnumBundle\DependencyInjection\CompilerPass\CollectEnumCompilerPass;
5+
use EnumBundle\DependencyInjection\CompilerPass\ConventionedEnumCollectorCompilerPass;
6+
use EnumBundle\DependencyInjection\CompilerPass\TaggedEnumCollectorCompilerPass;
67
use EnumBundle\DependencyInjection\EnumExtension;
78
use Symfony\Component\DependencyInjection\ContainerBuilder;
89
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -17,7 +18,15 @@ class EnumBundle extends Bundle
1718
*/
1819
public function build(ContainerBuilder $container)
1920
{
20-
$container->addCompilerPass(new CollectEnumCompilerPass);
21+
if ($bundles = $container->getParameter('enum.register_bundles')) {
22+
if (true === $bundles) {
23+
$bundles = $container->getParameter('kernel.bundles');
24+
} else {
25+
$bundles = (array) $bundles;
26+
}
27+
$container->addCompilerPass(new ConventionedEnumCollectorCompilerPass($bundles));
28+
}
29+
$container->addCompilerPass(new TaggedEnumCollectorCompilerPass);
2130
}
2231

2332
/**
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace EnumBundle\Tests\DependencyInjection\CompilerPass;
4+
5+
spl_autoload_register(function ($class) {
6+
$file = dirname(dirname(__DIR__)).'/Fixtures/Bundles/'.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
7+
if (file_exists($file)) {
8+
require $file;
9+
return true;
10+
}
11+
return false;
12+
});
13+
14+
use EnumBundle\DependencyInjection\CompilerPass\ConventionedEnumCollectorCompilerPass;
15+
use Prophecy\Argument;
16+
17+
/**
18+
* @author Yann Eugoné <[email protected]>
19+
*/
20+
class ConventionedEnumCollectorCompilerPassTest extends \PHPUnit_Framework_TestCase
21+
{
22+
/**
23+
* @dataProvider getBundles
24+
*/
25+
public function testCollectThroughBundles($bundle, $prefix)
26+
{
27+
$container = $this->prophesize('Symfony\Component\DependencyInjection\ContainerBuilder');
28+
29+
$namespace = (new \ReflectionClass($bundle))->getNamespaceName();
30+
31+
$container
32+
->setDefinition(
33+
$prefix.'.enum.dummy',
34+
Argument::allOf(
35+
Argument::type('Symfony\Component\DependencyInjection\Definition'),
36+
Argument::which('getTags', ['enum' => [[]]]),
37+
Argument::which('getClass', $namespace.'\Enum\DummyEnum')
38+
)
39+
)
40+
->shouldBeCalled();
41+
$container
42+
->setDefinition(
43+
$prefix.'.enum.customer_gender',
44+
Argument::allOf(
45+
Argument::type('Symfony\Component\DependencyInjection\Definition'),
46+
Argument::which('getTags', ['enum' => [[]]]),
47+
Argument::which('getClass', $namespace.'\Enum\Customer\GenderEnum')
48+
)
49+
)
50+
->shouldBeCalled();
51+
$container
52+
->setDefinition(
53+
$prefix.'.enum.customer_state',
54+
Argument::allOf(
55+
Argument::type('Symfony\Component\DependencyInjection\DefinitionDecorator'),
56+
Argument::which('getTags', ['enum' => [[]]]),
57+
Argument::which('getClass', $namespace.'\Enum\Customer\StateEnum'),
58+
Argument::which(
59+
'getArguments',
60+
[
61+
'customer.state.label_%s'
62+
]
63+
),
64+
Argument::which('getParent', 'enum.abstract_translated')
65+
)
66+
)
67+
->shouldBeCalled();
68+
69+
$compiler = new ConventionedEnumCollectorCompilerPass([$bundle]);
70+
$compiler->process($container->reveal());
71+
}
72+
73+
public function getBundles()
74+
{
75+
return [
76+
['AppBundle\AppBundle', 'app'],
77+
['Acme\AppBundle\AcmeAppBundle', 'acme_app'],
78+
['Acme\Bundle\AppBundle\AcmeAppBundle', 'acme_app'],
79+
];
80+
}
81+
}

0 commit comments

Comments
 (0)