feature #33128 [FrameworkBundle] Sort tagged services (krome162504)

This PR was merged into the 4.4 branch.

Discussion
----------

[FrameworkBundle] Sort tagged services

| Q             | A
| ------------- | ---
| Branch?       | 4.4  <!-- see below -->
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets |  https://github.com/symfony/symfony/issues/32439 <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        |  -

Hi

This PR it's to improve DX when `debug:container` command is use with tag argument by sorting them by priority (More details in linked issue).
Currently they are sort by alphabetical order.

Commits
-------

54cef2a3a3 [FrameworkBundle] Sort tagged service by priority
This commit is contained in:
Fabien Potencier 2019-09-25 21:10:01 +02:00
commit 098584a33c
11 changed files with 306 additions and 9 deletions

View File

@ -267,6 +267,25 @@ abstract class Descriptor implements DescriptorInterface
return $serviceIds;
}
protected function sortTaggedServicesByPriority(array $services): array
{
$maxPriority = [];
foreach ($services as $service => $tags) {
$maxPriority[$service] = 0;
foreach ($tags as $tag) {
$currentPriority = $tag['priority'] ?? 0;
if ($maxPriority[$service] < $currentPriority) {
$maxPriority[$service] = $currentPriority;
}
}
}
uasort($maxPriority, function ($a, $b) {
return $b <=> $a;
});
return array_keys($maxPriority);
}
/**
* Gets class description from a docblock.
*/

View File

@ -100,7 +100,9 @@ class JsonDescriptor extends Descriptor
*/
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$omitTags = isset($options['omit_tags']) && $options['omit_tags'];
$showArguments = isset($options['show_arguments']) && $options['show_arguments'];
@ -110,7 +112,7 @@ class JsonDescriptor extends Descriptor
$serviceIds = array_filter($serviceIds, $options['filter']);
}
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {

View File

@ -132,7 +132,9 @@ class MarkdownDescriptor extends Descriptor
}
$this->write($title."\n".str_repeat('=', \strlen($title)));
$serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$showArguments = isset($options['show_arguments']) && $options['show_arguments'];
$services = ['definitions' => [], 'aliases' => [], 'services' => []];
@ -140,7 +142,7 @@ class MarkdownDescriptor extends Descriptor
$serviceIds = array_filter($serviceIds, $options['filter']);
}
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {

View File

@ -191,7 +191,9 @@ class TextDescriptor extends Descriptor
$options['output']->title($title);
$serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$maxTags = [];
if (isset($options['filter'])) {
@ -230,7 +232,7 @@ class TextDescriptor extends Descriptor
$tableHeaders = array_merge(['Service ID'], $tagsNames, ['Class name']);
$tableRows = [];
$rawOutput = isset($options['raw_text']) && $options['raw_text'];
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$definition = $this->resolveServiceDefinition($builder, $serviceId);
$styledServiceId = $rawOutput ? $serviceId : sprintf('<fg=cyan>%s</fg=cyan>', OutputFormatter::escape($serviceId));

View File

@ -289,13 +289,14 @@ class XmlDescriptor extends Descriptor
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($containerXML = $dom->createElement('container'));
$serviceIds = $tag ? array_keys($builder->findTaggedServiceIds($tag)) : $builder->getServiceIds();
$serviceIds = $tag
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag))
: $this->sortServiceIds($builder->getServiceIds());
if ($filter) {
$serviceIds = array_filter($serviceIds, $filter);
}
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {

View File

@ -287,4 +287,25 @@ abstract class AbstractDescriptorTest extends TestCase
return $data;
}
/** @dataProvider getDescribeContainerBuilderWithPriorityTagsTestData */
public function testDescribeContainerBuilderWithPriorityTags(ContainerBuilder $builder, $expectedDescription, array $options): void
{
$this->assertDescription($expectedDescription, $builder, $options);
}
public function getDescribeContainerBuilderWithPriorityTagsTestData(): array
{
$variations = ['priority_tag' => ['tag' => 'tag1']];
$data = [];
foreach (ObjectsProvider::getContainerBuildersWithPriorityTags() as $name => $object) {
foreach ($variations as $suffix => $options) {
$file = sprintf('%s_%s.%s', trim($name, '.'), $suffix, $this->getFormat());
$description = file_get_contents(__DIR__.'/../../Fixtures/Descriptor/'.$file);
$data[] = [$object, $description, $options, $file];
}
}
return $data;
}
}

View File

@ -144,6 +144,50 @@ class ObjectsProvider
];
}
public static function getContainerBuildersWithPriorityTags()
{
$builder = new ContainerBuilder();
$builder->setDefinitions(self::getContainerDefinitionsWithPriorityTags());
return ['builder' => $builder];
}
public static function getContainerDefinitionsWithPriorityTags()
{
$definition1 = new Definition('Full\\Qualified\\Class1');
$definition2 = new Definition('Full\\Qualified\\Class2');
$definition3 = new Definition('Full\\Qualified\\Class3');
return [
'definition_1' => $definition1
->setPublic(true)
->setSynthetic(true)
->setFile('/path/to/file')
->setLazy(false)
->setAbstract(false)
->addTag('tag1', ['attr1' => 'val1', 'priority' => 30])
->addTag('tag1', ['attr2' => 'val2'])
->addTag('tag2')
->addMethodCall('setMailer', [new Reference('mailer')])
->setFactory([new Reference('factory.service'), 'get']),
'definition_2' => $definition2
->setPublic(true)
->setSynthetic(true)
->setFile('/path/to/file')
->setLazy(false)
->setAbstract(false)
->addTag('tag1', ['attr1' => 'val1', 'attr2' => 'val2', 'priority' => -20]),
'definition_3' => $definition3
->setPublic(true)
->setSynthetic(true)
->setFile('/path/to/file')
->setLazy(false)
->setAbstract(false)
->addTag('tag1', ['attr1' => 'val1', 'attr2' => 'val2', 'priority' => 0])
->addTag('tag1', ['attr3' => 'val3', 'priority' => 40]),
];
}
public static function getContainerAliases()
{
return [

View File

@ -0,0 +1,90 @@
{
"definitions": {
"definition_3": {
"class": "Full\\Qualified\\Class3",
"public": true,
"synthetic": true,
"lazy": false,
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"tags": [
{
"name": "tag1",
"parameters": {
"attr1": "val1",
"attr2": "val2",
"priority": 0
}
},
{
"name": "tag1",
"parameters": {
"attr3": "val3",
"priority": 40
}
}
]
},
"definition_1": {
"class": "Full\\Qualified\\Class1",
"public": true,
"synthetic": true,
"lazy": false,
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",
"calls": [
"setMailer"
],
"tags": [
{
"name": "tag1",
"parameters": {
"attr1": "val1",
"priority": 30
}
},
{
"name": "tag1",
"parameters": {
"attr2": "val2"
}
},
{
"name": "tag2",
"parameters": []
}
]
},
"definition_2": {
"class": "Full\\Qualified\\Class2",
"public": true,
"synthetic": true,
"lazy": false,
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"tags": [
{
"name": "tag1",
"parameters": {
"attr1": "val1",
"attr2": "val2",
"priority": -20
}
}
]
}
},
"aliases": [],
"services": []
}

View File

@ -0,0 +1,61 @@
Services with tag `tag1`
========================
Definitions
-----------
### definition_3
- Class: `Full\Qualified\Class3`
- Public: yes
- Synthetic: yes
- Lazy: no
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Tag: `tag1`
- Attr1: val1
- Attr2: val2
- Priority: 0
- Tag: `tag1`
- Attr3: val3
- Priority: 40
### definition_1
- Class: `Full\Qualified\Class1`
- Public: yes
- Synthetic: yes
- Lazy: no
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`
- Call: `setMailer`
- Tag: `tag1`
- Attr1: val1
- Priority: 30
- Tag: `tag1`
- Attr2: val2
- Tag: `tag2`
### definition_2
- Class: `Full\Qualified\Class2`
- Public: yes
- Synthetic: yes
- Lazy: no
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Tag: `tag1`
- Attr1: val1
- Attr2: val2
- Priority: -20

View File

@ -0,0 +1,14 @@
Symfony Container Services Tagged with "tag1" Tag
=================================================
-------------- ------- ------- ---------- ------- -----------------------
 Service ID   attr1   attr2   priority   attr3   Class name 
-------------- ------- ------- ---------- ------- -----------------------
definition_3 val1 val2 0 Full\Qualified\Class3
" 40 val3
definition_1 val1 30 Full\Qualified\Class1
" val2
definition_2 val1 val2 -20 Full\Qualified\Class2
-------------- ------- ------- ---------- ------- -----------------------

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<container>
<definition id="definition_3" class="Full\Qualified\Class3" public="true" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<tags>
<tag name="tag1">
<parameter name="attr1">val1</parameter>
<parameter name="attr2">val2</parameter>
<parameter name="priority">0</parameter>
</tag>
<tag name="tag1">
<parameter name="attr3">val3</parameter>
<parameter name="priority">40</parameter>
</tag>
</tags>
</definition>
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>
</calls>
<tags>
<tag name="tag1">
<parameter name="attr1">val1</parameter>
<parameter name="priority">30</parameter>
</tag>
<tag name="tag1">
<parameter name="attr2">val2</parameter>
</tag>
<tag name="tag2"/>
</tags>
</definition>
<definition id="definition_2" class="Full\Qualified\Class2" public="true" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<tags>
<tag name="tag1">
<parameter name="attr1">val1</parameter>
<parameter name="attr2">val2</parameter>
<parameter name="priority">-20</parameter>
</tag>
</tags>
</definition>
</container>