feature #18482 Created a trait to sort tagged services (iltar)

This PR was squashed before being merged into the 3.2-dev branch (closes #18482).

Discussion
----------

Created a trait to sort tagged services

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | ~
| License       | MIT
| Doc PR        | ~

When writing the `ControllerArgumentValueResolverPass`, I needed a sorting on `priority`. I ended up copying a method from another class. I noticed this was done more often and 99% of the code was the same. I've moved the most common notation into the trait and "used" the trait in the priority aware passes. This increases horizontal re-use and means people can also use this in their bundles.

The cases that were slightly different, are still working completely. I had to fix some tests because they returned an invalid value from the mocked find method.

Commits
-------

778a70b Created a trait to sort tagged services
This commit is contained in:
Fabien Potencier 2016-06-14 08:28:41 +02:00
commit f8dc4593a3
9 changed files with 158 additions and 119 deletions

View File

@ -11,9 +11,9 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers the cache warmers.
@ -22,6 +22,8 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class AddCacheWarmerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
/**
* {@inheritdoc}
*/
@ -31,20 +33,12 @@ class AddCacheWarmerPass implements CompilerPassInterface
return;
}
$warmers = array();
foreach ($container->findTaggedServiceIds('kernel.cache_warmer') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$warmers[$priority][] = new Reference($id);
}
$warmers = $this->findAndSortTaggedServices('kernel.cache_warmer', $container);
if (empty($warmers)) {
return;
}
// sort by priority and flatten
krsort($warmers);
$warmers = call_user_func_array('array_merge', $warmers);
$container->getDefinition('cache_warmer')->replaceArgument(0, $warmers);
}
}

View File

@ -12,8 +12,8 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority.
@ -23,23 +23,16 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class ConfigCachePass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container)
{
$resourceCheckers = array();
foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $tags) {
$priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0;
$resourceCheckers[$priority][] = new Reference($id);
}
$resourceCheckers = $this->findAndSortTaggedServices('config_cache.resource_checker', $container);
if (empty($resourceCheckers)) {
return;
}
// sort by priority and flatten
krsort($resourceCheckers);
$resourceCheckers = call_user_func_array('array_merge', $resourceCheckers);
$container->getDefinition('config_cache_factory')->replaceArgument(0, $resourceCheckers);
}
}

View File

@ -12,8 +12,8 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Gathers and configures the argument value resolvers.
@ -22,6 +22,8 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class ControllerArgumentValueResolverPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('argument_resolver')) {
@ -32,34 +34,4 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface
$argumentResolvers = $this->findAndSortTaggedServices('controller.argument_value_resolver', $container);
$definition->replaceArgument(1, $argumentResolvers);
}
/**
* 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

@ -12,8 +12,8 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds extractors to the property_info service.
@ -22,6 +22,8 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class PropertyInfoPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
/**
* {@inheritdoc}
*/
@ -45,34 +47,4 @@ class PropertyInfoPass implements CompilerPassInterface
$accessExtractors = $this->findAndSortTaggedServices('property_info.access_extractor', $container);
$definition->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

@ -11,9 +11,9 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as
@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class SerializerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('serializer')) {
@ -31,42 +33,17 @@ class SerializerPass implements CompilerPassInterface
// Looks for all the services tagged "serializer.normalizer" and adds them to the Serializer service
$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container);
if (empty($normalizers)) {
throw new \RuntimeException('You must tag at least one service as "serializer.normalizer" to use the Serializer service');
}
$container->getDefinition('serializer')->replaceArgument(0, $normalizers);
// Looks for all the services tagged "serializer.encoders" and adds them to the Serializer service
$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container);
if (empty($encoders)) {
throw new \RuntimeException('You must tag at least one service as "serializer.encoder" to use the Serializer service');
}
$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);
if (empty($services)) {
throw new \RuntimeException(sprintf('You must tag at least one service as "%s" to use the Serializer service', $tagName));
}
$sortedServices = array();
foreach ($services as $serviceId => $tags) {
foreach ($tags as $attributes) {
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
$sortedServices[$priority][] = new Reference($serviceId);
}
}
krsort($sortedServices);
// Flatten the array
return call_user_func_array('array_merge', $sortedServices);
}
}

View File

@ -21,7 +21,7 @@ class AddCacheWarmerPassTest extends \PHPUnit_Framework_TestCase
$services = array(
'my_cache_warmer_service1' => array(0 => array('priority' => 100)),
'my_cache_warmer_service2' => array(0 => array('priority' => 200)),
'my_cache_warmer_service3' => array(),
'my_cache_warmer_service3' => array(0 => array()),
);
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');

View File

@ -21,7 +21,7 @@ class ConfigCachePassTest extends \PHPUnit_Framework_TestCase
$services = array(
'checker_2' => array(0 => array('priority' => 100)),
'checker_1' => array(0 => array('priority' => 200)),
'checker_3' => array(),
'checker_3' => array(0 => array()),
);
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
@ -52,7 +52,6 @@ class ConfigCachePassTest extends \PHPUnit_Framework_TestCase
public function testThatCheckersCanBeMissing()
{
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
$container = $this->getMock(
'Symfony\Component\DependencyInjection\ContainerBuilder',
array('findTaggedServiceIds')

View File

@ -0,0 +1,47 @@
<?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\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Trait that allows a generic method to find and sort service by priority option in the tag.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
trait PriorityTaggedServiceTrait
{
/**
* Finds all services with the given tag name and order them by their priority.
*
* @param string $tagName
* @param ContainerBuilder $container
*
* @return Reference[]
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
{
$services = $container->findTaggedServiceIds($tagName);
$queue = new \SplPriorityQueue();
foreach ($services as $serviceId => $tags) {
foreach ($tags as $attributes) {
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
$queue->insert(new Reference($serviceId), $priority * -1);
}
}
return iterator_to_array($queue);
}
}

View File

@ -0,0 +1,85 @@
<?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\Component\DependencyInjection\Tests\Compiler;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class PriorityTaggedServiceTraitTest extends \PHPUnit_Framework_TestCase
{
public function testThatCacheWarmersAreProcessedInPriorityOrder()
{
$services = array(
'my_service1' => array(array('priority' => 100)),
'my_service2' => array(array('priority' => 200)),
'my_service3' => array(array('priority' => -500)),
'my_service4' => array(array()),
'my_service5' => array(array()),
'my_service6' => array(array('priority' => -500)),
'my_service7' => array(array('priority' => -499)),
'my_service8' => array(array('priority' => 1)),
'my_service9' => array(array()),
'my_service10' => array(array('priority' => -1000)),
'my_service11' => array(array('priority' => -1000)),
'my_service12' => array(array('priority' => -1000)),
'my_service13' => array(array('priority' => -1000)),
);
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
$container = $this->getMock(
'Symfony\Component\DependencyInjection\ContainerBuilder',
array('findTaggedServiceIds', 'getDefinition', 'hasDefinition')
);
$container
->expects($this->atLeastOnce())
->method('findTaggedServiceIds')
->will($this->returnValue($services));
$container
->expects($this->atLeastOnce())
->method('getDefinition')
->with('my_custom_tag')
->will($this->returnValue($definition));
$definition
->expects($this->once())
->method('replaceArgument')
->with(0, array(
new Reference('my_service2'),
new Reference('my_service1'),
new Reference('my_service8'),
new Reference('my_service4'),
new Reference('my_service5'),
new Reference('my_service9'),
new Reference('my_service7'),
new Reference('my_service3'),
new Reference('my_service6'),
new Reference('my_service10'),
new Reference('my_service11'),
new Reference('my_service12'),
new Reference('my_service13'),
));
(new PriorityTaggedServiceTraitImplementation())->test('my_custom_tag', $container);
}
}
class PriorityTaggedServiceTraitImplementation
{
use PriorityTaggedServiceTrait;
public function test($tagName, ContainerBuilder $container)
{
return $this->findAndSortTaggedServices($tagName, $container);
}
}