Skip to content

Commit 0a6d875

Browse files
dunglasfabpot
authored andcommitted
[FrameworkBundle] PropertyInfo support
1 parent fbb5bb8 commit 0a6d875

File tree

15 files changed

+270
-5
lines changed

15 files changed

+270
-5
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Bundle\FrameworkBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
18+
/**
19+
* Adds extractors to the property_info service.
20+
*
21+
* @author Kévin Dunglas <[email protected]>
22+
*/
23+
class PropertyInfoPass implements CompilerPassInterface
24+
{
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function process(ContainerBuilder $container)
29+
{
30+
if (!$container->hasDefinition('property_info')) {
31+
return;
32+
}
33+
34+
$listExtractors = $this->findAndSortTaggedServices('property_info.list_extractor', $container);
35+
$container->getDefinition('property_info')->replaceArgument(0, $listExtractors);
36+
37+
$typeExtractors = $this->findAndSortTaggedServices('property_info.type_extractor', $container);
38+
$container->getDefinition('property_info')->replaceArgument(1, $typeExtractors);
39+
40+
$descriptionExtractors = $this->findAndSortTaggedServices('property_info.description_extractor', $container);
41+
$container->getDefinition('property_info')->replaceArgument(2, $descriptionExtractors);
42+
43+
$accessExtractors = $this->findAndSortTaggedServices('property_info.access_extractor', $container);
44+
$container->getDefinition('property_info')->replaceArgument(3, $accessExtractors);
45+
}
46+
47+
/**
48+
* Finds all services with the given tag name and order them by their priority.
49+
*
50+
* @param string $tagName
51+
* @param ContainerBuilder $container
52+
*
53+
* @return array
54+
*/
55+
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
56+
{
57+
$services = $container->findTaggedServiceIds($tagName);
58+
59+
$sortedServices = array();
60+
foreach ($services as $serviceId => $tags) {
61+
foreach ($tags as $attributes) {
62+
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
63+
$sortedServices[$priority][] = new Reference($serviceId);
64+
}
65+
}
66+
67+
if (empty($sortedServices)) {
68+
return array();
69+
}
70+
71+
krsort($sortedServices);
72+
73+
// Flatten the array
74+
return call_user_func_array('array_merge', $sortedServices);
75+
}
76+
}

DependencyInjection/Compiler/SerializerPass.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ public function process(ContainerBuilder $container)
3838
$container->getDefinition('serializer')->replaceArgument(1, $encoders);
3939
}
4040

41+
/**
42+
* Finds all services with the given tag name and order them by their priority.
43+
*
44+
* @param string $tagName
45+
* @param ContainerBuilder $container
46+
*
47+
* @return array
48+
*
49+
* @throws \RuntimeException
50+
*/
4151
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
4252
{
4353
$services = $container->findTaggedServiceIds($tagName);
@@ -48,8 +58,8 @@ private function findAndSortTaggedServices($tagName, ContainerBuilder $container
4858

4959
$sortedServices = array();
5060
foreach ($services as $serviceId => $tags) {
51-
foreach ($tags as $tag) {
52-
$priority = isset($tag['priority']) ? $tag['priority'] : 0;
61+
foreach ($tags as $attributes) {
62+
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
5363
$sortedServices[$priority][] = new Reference($serviceId);
5464
}
5565
}

DependencyInjection/Configuration.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ public function getConfigTreeBuilder()
178178
$this->addAnnotationsSection($rootNode);
179179
$this->addSerializerSection($rootNode);
180180
$this->addPropertyAccessSection($rootNode);
181+
$this->addPropertyInfoSection($rootNode);
181182

182183
return $treeBuilder;
183184
}
@@ -723,4 +724,16 @@ private function addPropertyAccessSection(ArrayNodeDefinition $rootNode)
723724
->end()
724725
;
725726
}
727+
728+
private function addPropertyInfoSection(ArrayNodeDefinition $rootNode)
729+
{
730+
$rootNode
731+
->children()
732+
->arrayNode('property_info')
733+
->info('Property info configuration')
734+
->canBeEnabled()
735+
->end()
736+
->end()
737+
;
738+
}
726739
}

DependencyInjection/FrameworkExtension.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ public function load(array $configs, ContainerBuilder $container)
131131
$this->registerSerializerConfiguration($config['serializer'], $container, $loader);
132132
}
133133

134+
if (isset($config['property_info'])) {
135+
$this->registerPropertyInfoConfiguration($config['property_info'], $container, $loader);
136+
}
137+
134138
$loader->load('debug_prod.xml');
135139
$definition = $container->findDefinition('debug.debug_handlers_listener');
136140

@@ -986,6 +990,28 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
986990
}
987991
}
988992

993+
/**
994+
* Loads property info configuration.
995+
*
996+
* @param array $config
997+
* @param ContainerBuilder $container
998+
* @param XmlFileLoader $loader
999+
*/
1000+
private function registerPropertyInfoConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
1001+
{
1002+
if (!$config['enabled']) {
1003+
return;
1004+
}
1005+
1006+
$loader->load('property_info.xml');
1007+
1008+
if (class_exists('phpDocumentor\Reflection\ClassReflector')) {
1009+
$definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor');
1010+
$definition->addTag('property_info.description_extractor', array('priority' => -1000));
1011+
$definition->addTag('property_info.type_extractor', array('priority' => -1001));
1012+
}
1013+
}
1014+
9891015
/**
9901016
* Returns the base path for the XSD files.
9911017
*

FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
1616
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
1717
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
18+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
1819
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
1920
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass;
2021
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
@@ -90,6 +91,7 @@ public function build(ContainerBuilder $container)
9091
$container->addCompilerPass(new TranslationDumperPass());
9192
$container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING);
9293
$container->addCompilerPass(new SerializerPass());
94+
$container->addCompilerPass(new PropertyInfoPass());
9395

9496
if ($container->getParameter('kernel.debug')) {
9597
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);

Resources/config/property_info.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="property_info" class="Symfony\Component\PropertyInfo\PropertyInfoExtractor" >
9+
<argument type="collection" />
10+
<argument type="collection" />
11+
<argument type="collection" />
12+
<argument type="collection" />
13+
</service>
14+
15+
<!-- Extractor -->
16+
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\ReflectionExtractor" public="false">
17+
<tag name="property_info.list_extractor" priority="-1000" />
18+
<tag name="property_info.type_extractor" priority="-1000" />
19+
<tag name="property_info.access_extractor" priority="-1000" />
20+
</service>
21+
</services>
22+
</container>

Resources/config/schema/symfony-1.0.xsd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<xsd:element name="annotations" type="annotations" minOccurs="0" maxOccurs="1" />
3434
<xsd:element name="property-access" type="property_access" minOccurs="0" maxOccurs="1" />
3535
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
36+
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
3637
</xsd:all>
3738

3839
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@@ -220,4 +221,8 @@
220221
<xsd:attribute name="enable-annotations" type="xsd:boolean" />
221222
<xsd:attribute name="name-converter" type="xsd:string" />
222223
</xsd:complexType>
224+
225+
<xsd:complexType name="property_info">
226+
<xsd:attribute name="enabled" type="xsd:boolean" />
227+
</xsd:complexType>
223228
</xsd:schema>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
13+
14+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
15+
use Symfony\Component\DependencyInjection\Reference;
16+
17+
class PropertyInfoPassTest extends \PHPUnit_Framework_TestCase
18+
{
19+
public function testServicesAreOrderedAccordingToPriority()
20+
{
21+
$services = array(
22+
'n3' => array('tag' => array()),
23+
'n1' => array('tag' => array('priority' => 200)),
24+
'n2' => array('tag' => array('priority' => 100)),
25+
);
26+
27+
$expected = array(
28+
new Reference('n1'),
29+
new Reference('n2'),
30+
new Reference('n3'),
31+
);
32+
33+
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', array('findTaggedServiceIds'));
34+
35+
$container->expects($this->any())
36+
->method('findTaggedServiceIds')
37+
->will($this->returnValue($services));
38+
39+
$propertyInfoPass = new PropertyInfoPass();
40+
41+
$method = new \ReflectionMethod(
42+
'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass',
43+
'findAndSortTaggedServices'
44+
);
45+
$method->setAccessible(true);
46+
47+
$actual = $method->invoke($propertyInfoPass, 'tag', $container);
48+
49+
$this->assertEquals($expected, $actual);
50+
}
51+
52+
public function testReturningEmptyArrayWhenNoService()
53+
{
54+
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', array('findTaggedServiceIds'));
55+
56+
$container->expects($this->any())
57+
->method('findTaggedServiceIds')
58+
->will($this->returnValue(array()));
59+
60+
$propertyInfoPass = new PropertyInfoPass();
61+
62+
$method = new \ReflectionMethod(
63+
'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass',
64+
'findAndSortTaggedServices'
65+
);
66+
$method->setAccessible(true);
67+
68+
$actual = $method->invoke($propertyInfoPass, 'tag', $container);
69+
70+
$this->assertEquals(array(), $actual);
71+
}
72+
}

Tests/DependencyInjection/Compiler/SerializerPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public function testServicesAreOrderedAccordingToPriority()
8787

8888
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', array('findTaggedServiceIds'));
8989

90-
$container->expects($this->atLeastOnce())
90+
$container->expects($this->any())
9191
->method('findTaggedServiceIds')
9292
->will($this->returnValue($services));
9393

Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ protected static function getBundleDefaultConfig()
179179
'magic_call' => false,
180180
'throw_exception_on_invalid_index' => false,
181181
),
182+
'property_info' => array(
183+
'enabled' => false,
184+
),
182185
'assets' => array(
183186
'version' => null,
184187
'version_format' => '%%s?%%s',

0 commit comments

Comments
 (0)