Skip to content

Commit 0f18bf6

Browse files
[DependencyInjection][Routing] Define array-shapes to help writing PHP configs using yaml-like arrays
1 parent 9359b31 commit 0f18bf6

26 files changed

+660
-194
lines changed

composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@
197197
"Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/",
198198
"Symfony\\Bundle\\": "src/Symfony/Bundle/",
199199
"Symfony\\Component\\": "src/Symfony/Component/",
200+
"Symfony\\Config\\": [
201+
"src/Symfony/Component/DependencyInjection/Loader/Config/",
202+
"src/Symfony/Component/Routing/Loader/Config/"
203+
],
200204
"Symfony\\Runtime\\Symfony\\Component\\": "src/Symfony/Component/Runtime/Internal/"
201205
},
202206
"files": [

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
* Allow multiple `#[AsDecorator]` attributes
1313
* Handle returning arrays and config-builders from config files
1414
* Handle declaring services using PHP arrays that follow the same shape as corresponding yaml files
15+
* Add `ServicesConfig` to help writing PHP configs using yaml-like array-shapes
1516
* Deprecate using `$this` or its internal scope from PHP config files; use the `$loader` variable instead
1617
* Deprecate XML configuration format, use YAML or PHP instead
1718
* Deprecate `ExtensionInterface::getXsdValidationBasePath()` and `getNamespace()`
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Config;
13+
14+
use Symfony\Component\DependencyInjection\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\ExpressionLanguage\Expression;
19+
20+
require_once __DIR__.\DIRECTORY_SEPARATOR.'functions.php';
21+
22+
/**
23+
* @phpstan-type Imports list<string|array{
24+
* resource: string,
25+
* type?: string|null,
26+
* ignore_errors?: bool,
27+
* }>
28+
* @phpstan-type Parameters array<string, scalar|\UnitEnum|array<scalar|\UnitEnum|array|null>|null>
29+
* @phpstan-type Services array<string, Definition|Alias|Prototype|Stack>|array<class-string, Arguments|null>
30+
* @phpstan-type Defaults array{
31+
* public?: bool,
32+
* tags?: Tags,
33+
* resource_tags?: Tags,
34+
* autowire?: bool,
35+
* autoconfigure?: bool,
36+
* bind?: array<string, mixed>,
37+
* }
38+
* @phpstan-type Instanceof array{
39+
* shared?: bool,
40+
* lazy?: bool|string,
41+
* public?: bool,
42+
* properties?: array<string, mixed>,
43+
* configurator?: Callback,
44+
* calls?: list<Call>,
45+
* tags?: Tags,
46+
* resource_tags?: Tags,
47+
* autowire?: bool,
48+
* bind?: array<string, mixed>,
49+
* constructor?: string,
50+
* }
51+
* @phpstan-type Definition array{
52+
* class?: string,
53+
* file?: string,
54+
* parent?: string,
55+
* shared?: bool,
56+
* synthetic?: bool,
57+
* lazy?: bool|string,
58+
* public?: bool,
59+
* abstract?: bool,
60+
* deprecated?: Deprecation,
61+
* factory?: Callback,
62+
* configurator?: Callback,
63+
* arguments?: Arguments,
64+
* properties?: array<string, mixed>,
65+
* calls?: list<Call>,
66+
* tags?: Tags,
67+
* resource_tags?: Tags,
68+
* decorates?: string,
69+
* decoration_inner_name?: string,
70+
* decoration_priority?: int,
71+
* decoration_on_invalid?: 'exception'|'ignore'|null|ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE|ContainerInterface::IGNORE_ON_INVALID_REFERENCE|ContainerInterface::NULL_ON_INVALID_REFERENCE,
72+
* autowire?: bool,
73+
* autoconfigure?: bool,
74+
* bind?: array<string, mixed>,
75+
* constructor?: string,
76+
* from_callable?: mixed,
77+
* }
78+
* @phpstan-type Alias string|array{
79+
* alias: string,
80+
* public?: bool,
81+
* deprecated?: Deprecation,
82+
* }
83+
* @phpstan-type Prototype array{
84+
* resource: string,
85+
* namespace?: string,
86+
* exclude?: string|list<string>,
87+
* parent?: string,
88+
* shared?: bool,
89+
* lazy?: bool|string,
90+
* public?: bool,
91+
* abstract?: bool,
92+
* deprecated?: Deprecation,
93+
* factory?: Callback,
94+
* arguments?: Arguments,
95+
* properties?: array<string, mixed>,
96+
* configurator?: Callback,
97+
* calls?: list<Call>,
98+
* tags?: Tags,
99+
* resource_tags?: Tags,
100+
* autowire?: bool,
101+
* autoconfigure?: bool,
102+
* bind?: array<string, mixed>,
103+
* constructor?: string,
104+
* }
105+
* @phpstan-type Stack array{
106+
* stack: list<Definition|Alias|Prototype|array<class-string, Arguments|null>>,
107+
* public?: bool,
108+
* deprecated?: Deprecation,
109+
* }
110+
* @phpstan-type Arguments list<mixed>|array<string, mixed>
111+
* @phpstan-type Callback string|array{0:string|Reference|ReferenceConfigurator,1:string}|\Closure|Reference|ReferenceConfigurator|Expression
112+
* @phpstan-type Tags list<string|array<string, array<string, mixed>>>
113+
* @phpstan-type Deprecation array{package: string, version: string, message?: string}
114+
* @phpstan-type Call array<string, Arguments>|array{0:string, 1?:Arguments, 2?:bool}|array{method:string, arguments?:Arguments, returns_clone?:bool}
115+
*/
116+
class ServicesConfig
117+
{
118+
public readonly array $services;
119+
120+
/**
121+
* @param Services $services
122+
* @param Imports $imports
123+
* @param Parameters $parameters
124+
* @param Defaults $defaults
125+
* @param Instanceof $instanceof
126+
*/
127+
public function __construct(
128+
array $services = [],
129+
public readonly array $imports = [],
130+
public readonly array $parameters = [],
131+
array $defaults = [],
132+
array $instanceof = [],
133+
) {
134+
if (isset($services['_defaults']) || isset($services['_instanceof'])) {
135+
throw new InvalidArgumentException('The $services argument should not contain "_defaults" or "_instanceof" keys, use the $defaults and $instanceof parameters instead.');
136+
}
137+
138+
$services['_defaults'] = $defaults;
139+
$services['_instanceof'] = $instanceof;
140+
$this->services = $services;
141+
}
142+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Config;
13+
14+
use Symfony\Component\Config\Loader\ParamConfigurator;
15+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
16+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
17+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
18+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
19+
use Symfony\Component\DependencyInjection\Definition;
20+
use Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator;
21+
use Symfony\Component\DependencyInjection\Loader\Configurator\ClosureReferenceConfigurator;
22+
use Symfony\Component\DependencyInjection\Loader\Configurator\EnvConfigurator;
23+
use Symfony\Component\DependencyInjection\Loader\Configurator\InlineServiceConfigurator;
24+
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
25+
use Symfony\Component\ExpressionLanguage\Expression;
26+
27+
/**
28+
* Creates a parameter.
29+
*/
30+
function param(string $name): ParamConfigurator
31+
{
32+
return new ParamConfigurator($name);
33+
}
34+
35+
/**
36+
* Creates a reference to a service.
37+
*/
38+
function service(string $serviceId): ReferenceConfigurator
39+
{
40+
return new ReferenceConfigurator($serviceId);
41+
}
42+
43+
/**
44+
* Creates an inline service.
45+
*/
46+
function inline_service(?string $class = null): InlineServiceConfigurator
47+
{
48+
return new InlineServiceConfigurator(new Definition($class));
49+
}
50+
51+
/**
52+
* Creates a service locator.
53+
*
54+
* @param array<ReferenceConfigurator|InlineServiceConfigurator> $values
55+
*/
56+
function service_locator(array $values): ServiceLocatorArgument
57+
{
58+
$values = AbstractConfigurator::processValue($values, true);
59+
60+
return new ServiceLocatorArgument($values);
61+
}
62+
63+
/**
64+
* Creates a lazy iterator.
65+
*
66+
* @param ReferenceConfigurator[] $values
67+
*/
68+
function iterator(array $values): IteratorArgument
69+
{
70+
return new IteratorArgument(AbstractConfigurator::processValue($values, true));
71+
}
72+
73+
/**
74+
* Creates a lazy iterator by tag name.
75+
*/
76+
function tagged_iterator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): TaggedIteratorArgument
77+
{
78+
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf);
79+
}
80+
81+
/**
82+
* Creates a service locator by tag name.
83+
*/
84+
function tagged_locator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): ServiceLocatorArgument
85+
{
86+
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
87+
}
88+
89+
/**
90+
* Creates an expression.
91+
*/
92+
function expr(string $expression): Expression
93+
{
94+
return new Expression($expression);
95+
}
96+
97+
/**
98+
* Creates an abstract argument.
99+
*/
100+
function abstract_arg(string $description): AbstractArgument
101+
{
102+
return new AbstractArgument($description);
103+
}
104+
105+
/**
106+
* Creates an environment variable reference.
107+
*/
108+
function env(string $name): EnvConfigurator
109+
{
110+
return new EnvConfigurator($name);
111+
}
112+
113+
/**
114+
* Creates a closure service reference.
115+
*/
116+
function service_closure(string $serviceId): ClosureReferenceConfigurator
117+
{
118+
return new ClosureReferenceConfigurator($serviceId);
119+
}
120+
121+
/**
122+
* Creates a closure.
123+
*/
124+
function closure(string|array|\Closure|ReferenceConfigurator|Expression $callable): InlineServiceConfigurator
125+
{
126+
return (new InlineServiceConfigurator(new Definition('Closure')))
127+
->factory(['Closure', 'fromCallable'])
128+
->args([$callable]);
129+
}

src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ public static function processValue(mixed $value, bool $allowServices = false):
101101
case $value instanceof \UnitEnum:
102102
return $value;
103103

104+
case $value instanceof \Closure:
105+
return self::processClosure($value);
106+
104107
case $value instanceof ArgumentInterface:
105108
case $value instanceof Definition:
106109
case $value instanceof Expression:
@@ -120,7 +123,7 @@ public static function processValue(mixed $value, bool $allowServices = false):
120123
*
121124
* @throws InvalidArgumentException if the closure is anonymous or references a non-static method
122125
*/
123-
final public static function processClosure(\Closure $closure): callable
126+
private static function processClosure(\Closure $closure): callable
124127
{
125128
$function = new \ReflectionFunction($closure);
126129
if ($function->isAnonymous()) {

0 commit comments

Comments
 (0)