Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #36373 [DI] add syntax to stack decorators (nicolas-grekas)
This PR was merged into the 5.1-dev branch. Discussion ---------- [DI] add syntax to stack decorators | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #30599 | License | MIT | Doc PR | - Declare this: ```yaml services: my_stack_of_decorators: stack: - class: App\ExternalDecorator - class: App\InternalDecorator - class: App\DecoratoredClass ``` And get this: ![image](https://user-images.githubusercontent.com/243674/78615803-b8c8e580-7872-11ea-95c2-22cb78f88ca8.png) The PR is now ready with support for Yaml, XML and the PHP-DSL. It needs #36388, #36392 and #36389 to pass, and relates to #36390 to be DX-friendly. The new syntax now supports composable stacks - i.e stack you can reuse in the middle of another stack. RIP middleware, simple decorators FTW :) From the test cases: ```yaml services: reusable_stack: stack: - class: stdClass properties: label: A inner: '@.inner' - class: stdClass properties: label: B inner: '@.inner' concrete_stack: stack: - parent: reusable_stack - class: stdClass properties: label: C ``` This will create a service similar to: ```php (object) [ 'label' => 'A', 'inner' => (object) [ 'label' => 'B', 'inner' => (object) [ 'label' => 'C', ] ], ]; ``` When used together with autowiring, this is enough to declare a stack of decorators: ```yaml services: my_processing_stack: stack: - App\ExternalDecorator: ~ - App\InternalDecorator: ~ - App\TheDecoratedClass: ~ ``` See fixtures for the other configuration formats. See also https://twitter.com/nicolasgrekas/status/1248198573998604288 Todo: - [x] rebase on top of #36388, #36392 and #36389 once they are merged - [x] test declaring deeper nested stacks Commits ------- 98eeeae [DI] add syntax to stack decorators
- Loading branch information
Showing
14 changed files
with
559 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
src/Symfony/Component/DependencyInjection/Compiler/ResolveDecoratorStackPass.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<?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\Alias; | ||
use Symfony\Component\DependencyInjection\ChildDefinition; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; | ||
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
/** | ||
* @author Nicolas Grekas <p@tchwork.com> | ||
*/ | ||
class ResolveDecoratorStackPass implements CompilerPassInterface | ||
{ | ||
private $tag; | ||
|
||
public function __construct(string $tag = 'container.stack') | ||
{ | ||
$this->tag = $tag; | ||
} | ||
|
||
public function process(ContainerBuilder $container) | ||
{ | ||
$stacks = []; | ||
|
||
foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) { | ||
$definition = $container->getDefinition($id); | ||
|
||
if (!$definition instanceof ChildDefinition) { | ||
throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag)); | ||
} | ||
|
||
if (!$stack = $definition->getArguments()) { | ||
throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id)); | ||
} | ||
|
||
$stacks[$id] = $stack; | ||
} | ||
|
||
if (!$stacks) { | ||
return; | ||
} | ||
|
||
$resolvedDefinitions = []; | ||
|
||
foreach ($container->getDefinitions() as $id => $definition) { | ||
if (!isset($stacks[$id])) { | ||
$resolvedDefinitions[$id] = $definition; | ||
continue; | ||
} | ||
|
||
foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) { | ||
$resolvedDefinitions[$k] = $v; | ||
} | ||
|
||
$alias = $container->setAlias($id, $k); | ||
|
||
if ($definition->getChanges()['public'] ?? false) { | ||
$alias->setPublic($definition->isPublic()); | ||
} | ||
|
||
if ($definition->isDeprecated()) { | ||
$alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%'))); | ||
} | ||
} | ||
|
||
$container->setDefinitions($resolvedDefinitions); | ||
} | ||
|
||
private function resolveStack(array $stacks, array $path): array | ||
{ | ||
$definitions = []; | ||
$id = end($path); | ||
$prefix = '.'.$id.'.'; | ||
|
||
if (!isset($stacks[$id])) { | ||
return [$id => new ChildDefinition($id)]; | ||
} | ||
|
||
if (key($path) !== $searchKey = array_search($id, $path)) { | ||
throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey)); | ||
} | ||
|
||
foreach ($stacks[$id] as $k => $definition) { | ||
if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) { | ||
$path[] = $definition->getParent(); | ||
$definition = unserialize(serialize($definition)); // deep clone | ||
} elseif ($definition instanceof Definition) { | ||
$definitions[$decoratedId = $prefix.$k] = $definition; | ||
continue; | ||
} elseif ($definition instanceof Reference || $definition instanceof Alias) { | ||
$path[] = (string) $definition; | ||
} else { | ||
throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition))); | ||
} | ||
|
||
$p = $prefix.$k; | ||
|
||
foreach ($this->resolveStack($stacks, $path) as $k => $v) { | ||
$definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k); | ||
$definition = null; | ||
} | ||
array_pop($path); | ||
} | ||
|
||
if (1 === \count($path)) { | ||
foreach ($definitions as $k => $definition) { | ||
$definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId); | ||
} | ||
$definition->setDecoratedService(null); | ||
} | ||
|
||
return $definitions; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.