Skip to content

Commit

Permalink
feature #18482 Created a trait to sort tagged services (iltar)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
fabpot committed Jun 14, 2016
2 parents 31678c9 + 778a70b commit f8dc459
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 119 deletions.
Expand Up @@ -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.
Expand All @@ -22,6 +22,8 @@
*/
class AddCacheWarmerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

/**
* {@inheritdoc}
*/
Expand All @@ -31,20 +33,12 @@ public function process(ContainerBuilder $container)
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);
}
}
Expand Up @@ -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.
Expand All @@ -23,23 +23,16 @@
*/
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);
}
}
Expand Up @@ -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.
Expand All @@ -22,6 +22,8 @@
*/
class ControllerArgumentValueResolverPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('argument_resolver')) {
Expand All @@ -32,34 +34,4 @@ public function process(ContainerBuilder $container)
$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);
}
}
Expand Up @@ -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.
Expand All @@ -22,6 +22,8 @@
*/
class PropertyInfoPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

/**
* {@inheritdoc}
*/
Expand All @@ -45,34 +47,4 @@ public function process(ContainerBuilder $container)
$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);
}
}
Expand Up @@ -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
Expand All @@ -23,6 +23,8 @@
*/
class SerializerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('serializer')) {
Expand All @@ -31,42 +33,17 @@ public function process(ContainerBuilder $container)

// 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);
$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);
}
if (empty($encoders)) {
throw new \RuntimeException('You must tag at least one service as "serializer.encoder" to use the Serializer service');
}

krsort($sortedServices);

// Flatten the array
return call_user_func_array('array_merge', $sortedServices);
$container->getDefinition('serializer')->replaceArgument(1, $encoders);
}
}
Expand Up @@ -21,7 +21,7 @@ public function testThatCacheWarmersAreProcessedInPriorityOrder()
$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');
Expand Down
Expand Up @@ -21,7 +21,7 @@ public function testThatCheckersAreProcessedInPriorityOrder()
$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');
Expand Down Expand Up @@ -52,7 +52,6 @@ public function testThatCheckersAreProcessedInPriorityOrder()

public function testThatCheckersCanBeMissing()
{
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
$container = $this->getMock(
'Symfony\Component\DependencyInjection\ContainerBuilder',
array('findTaggedServiceIds')
Expand Down
@@ -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);
}
}

0 comments on commit f8dc459

Please sign in to comment.