[FrameworkBundle] PropertyInfo support

This commit is contained in:
Kévin Dunglas 2015-09-28 13:22:50 +02:00 committed by Fabien Potencier
parent 864a58faa3
commit f84a92a601
15 changed files with 270 additions and 5 deletions

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds extractors to the property_info service.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('property_info')) {
return;
}
$listExtractors = $this->findAndSortTaggedServices('property_info.list_extractor', $container);
$container->getDefinition('property_info')->replaceArgument(0, $listExtractors);
$typeExtractors = $this->findAndSortTaggedServices('property_info.type_extractor', $container);
$container->getDefinition('property_info')->replaceArgument(1, $typeExtractors);
$descriptionExtractors = $this->findAndSortTaggedServices('property_info.description_extractor', $container);
$container->getDefinition('property_info')->replaceArgument(2, $descriptionExtractors);
$accessExtractors = $this->findAndSortTaggedServices('property_info.access_extractor', $container);
$container->getDefinition('property_info')->replaceArgument(3, $accessExtractors);
}
/**
* Finds all services with the given tag name and order them by their priority.
*
* @param string $tagName
* @param ContainerBuilder $container
*
* @return array
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
{
$services = $container->findTaggedServiceIds($tagName);
$sortedServices = array();
foreach ($services as $serviceId => $tags) {
foreach ($tags as $attributes) {
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
$sortedServices[$priority][] = new Reference($serviceId);
}
}
if (empty($sortedServices)) {
return array();
}
krsort($sortedServices);
// Flatten the array
return call_user_func_array('array_merge', $sortedServices);
}
}

View File

@ -38,6 +38,16 @@ class SerializerPass implements CompilerPassInterface
$container->getDefinition('serializer')->replaceArgument(1, $encoders);
}
/**
* Finds all services with the given tag name and order them by their priority.
*
* @param string $tagName
* @param ContainerBuilder $container
*
* @return array
*
* @throws \RuntimeException
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
{
$services = $container->findTaggedServiceIds($tagName);
@ -48,8 +58,8 @@ class SerializerPass implements CompilerPassInterface
$sortedServices = array();
foreach ($services as $serviceId => $tags) {
foreach ($tags as $tag) {
$priority = isset($tag['priority']) ? $tag['priority'] : 0;
foreach ($tags as $attributes) {
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
$sortedServices[$priority][] = new Reference($serviceId);
}
}

View File

@ -178,6 +178,7 @@ class Configuration implements ConfigurationInterface
$this->addAnnotationsSection($rootNode);
$this->addSerializerSection($rootNode);
$this->addPropertyAccessSection($rootNode);
$this->addPropertyInfoSection($rootNode);
return $treeBuilder;
}
@ -723,4 +724,16 @@ class Configuration implements ConfigurationInterface
->end()
;
}
private function addPropertyInfoSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('property_info')
->info('Property info configuration')
->canBeEnabled()
->end()
->end()
;
}
}

View File

@ -131,6 +131,10 @@ class FrameworkExtension extends Extension
$this->registerSerializerConfiguration($config['serializer'], $container, $loader);
}
if (isset($config['property_info'])) {
$this->registerPropertyInfoConfiguration($config['property_info'], $container, $loader);
}
$loader->load('debug_prod.xml');
$definition = $container->findDefinition('debug.debug_handlers_listener');
@ -986,6 +990,28 @@ class FrameworkExtension extends Extension
}
}
/**
* Loads property info configuration.
*
* @param array $config
* @param ContainerBuilder $container
* @param XmlFileLoader $loader
*/
private function registerPropertyInfoConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
if (!$config['enabled']) {
return;
}
$loader->load('property_info.xml');
if (class_exists('phpDocumentor\Reflection\ClassReflector')) {
$definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor');
$definition->addTag('property_info.description_extractor', array('priority' => -1000));
$definition->addTag('property_info.type_extractor', array('priority' => -1001));
}
}
/**
* Returns the base path for the XSD files.
*

View File

@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintVal
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
@ -90,6 +91,7 @@ class FrameworkBundle extends Bundle
$container->addCompilerPass(new TranslationDumperPass());
$container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new SerializerPass());
$container->addCompilerPass(new PropertyInfoPass());
if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);

View File

@ -0,0 +1,22 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="property_info" class="Symfony\Component\PropertyInfo\PropertyInfoExtractor" >
<argument type="collection" />
<argument type="collection" />
<argument type="collection" />
<argument type="collection" />
</service>
<!-- Extractor -->
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\ReflectionExtractor" public="false">
<tag name="property_info.list_extractor" priority="-1000" />
<tag name="property_info.type_extractor" priority="-1000" />
<tag name="property_info.access_extractor" priority="-1000" />
</service>
</services>
</container>

View File

@ -33,6 +33,7 @@
<xsd:element name="annotations" type="annotations" minOccurs="0" maxOccurs="1" />
<xsd:element name="property-access" type="property_access" minOccurs="0" maxOccurs="1" />
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@ -220,4 +221,8 @@
<xsd:attribute name="enable-annotations" type="xsd:boolean" />
<xsd:attribute name="name-converter" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="property_info">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
use Symfony\Component\DependencyInjection\Reference;
class PropertyInfoPassTest extends \PHPUnit_Framework_TestCase
{
public function testServicesAreOrderedAccordingToPriority()
{
$services = array(
'n3' => array('tag' => array()),
'n1' => array('tag' => array('priority' => 200)),
'n2' => array('tag' => array('priority' => 100)),
);
$expected = array(
new Reference('n1'),
new Reference('n2'),
new Reference('n3'),
);
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', array('findTaggedServiceIds'));
$container->expects($this->any())
->method('findTaggedServiceIds')
->will($this->returnValue($services));
$propertyInfoPass = new PropertyInfoPass();
$method = new \ReflectionMethod(
'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass',
'findAndSortTaggedServices'
);
$method->setAccessible(true);
$actual = $method->invoke($propertyInfoPass, 'tag', $container);
$this->assertEquals($expected, $actual);
}
public function testReturningEmptyArrayWhenNoService()
{
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', array('findTaggedServiceIds'));
$container->expects($this->any())
->method('findTaggedServiceIds')
->will($this->returnValue(array()));
$propertyInfoPass = new PropertyInfoPass();
$method = new \ReflectionMethod(
'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass',
'findAndSortTaggedServices'
);
$method->setAccessible(true);
$actual = $method->invoke($propertyInfoPass, 'tag', $container);
$this->assertEquals(array(), $actual);
}
}

View File

@ -87,7 +87,7 @@ class SerializerPassTest extends \PHPUnit_Framework_TestCase
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', array('findTaggedServiceIds'));
$container->expects($this->atLeastOnce())
$container->expects($this->any())
->method('findTaggedServiceIds')
->will($this->returnValue($services));

View File

@ -179,6 +179,9 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'magic_call' => false,
'throw_exception_on_invalid_index' => false,
),
'property_info' => array(
'enabled' => false,
),
'assets' => array(
'version' => null,
'version_format' => '%%s?%%s',

View File

@ -0,0 +1,7 @@
<?php
$container->loadFromExtension('framework', array(
'property_info' => array(
'enabled' => true,
),
));

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:property-info enabled="true" />
</framework:config>
</container>

View File

@ -0,0 +1,3 @@
framework:
property_info:
enabled: true

View File

@ -488,6 +488,18 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertFalse($container->hasDefinition('serializer'));
}
public function testPropertyInfoDisabled()
{
$container = $this->createContainerFromFile('default_config');
$this->assertFalse($container->has('property_info'));
}
public function testPropertyInfoEnabled()
{
$container = $this->createContainerFromFile('property_info');
$this->assertTrue($container->has('property_info'));
}
protected function createContainer(array $data = array())
{
return new ContainerBuilder(new ParameterBag(array_merge(array(

View File

@ -47,7 +47,9 @@
"symfony/expression-language": "~2.6|~3.0.0",
"symfony/process": "~2.0,>=2.0.5|~3.0.0",
"symfony/validator": "~2.5|~3.0.0",
"symfony/yaml": "~2.0,>=2.0.5|~3.0.0"
"symfony/yaml": "~2.0,>=2.0.5|~3.0.0",
"symfony/property-info": "~2.8|~3.0.0",
"phpdocumentor/reflection": "^1.0.7"
},
"suggest": {
"symfony/console": "For using the console commands",
@ -55,7 +57,8 @@
"symfony/form": "For using forms",
"symfony/serializer": "For using the serializer service",
"symfony/validator": "For using validation",
"symfony/yaml": "For using the debug:config and lint:yaml commands"
"symfony/yaml": "For using the debug:config and lint:yaml commands",
"symfony/property-info": "For using the property_info_extractor service"
},
"autoload": {
"psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" }