1616use Symfony \Component \DependencyInjection \Compiler \CompilerPassInterface ;
1717use Symfony \Component \DependencyInjection \ContainerBuilder ;
1818use Symfony \Component \DependencyInjection \Extension \ConfigurationExtensionInterface ;
19+ use Symfony \Component \DependencyInjection \Extension \ExtensionInterface ;
1920use Symfony \Component \DependencyInjection \Loader \Configurator \AppReference ;
21+ use Symfony \Component \Routing \Loader \Configurator \RoutesReference ;
2022
2123/**
2224 * @internal
2325 */
2426class PhpConfigReferenceDumpPass implements CompilerPassInterface
2527{
26- private const REFERENCE_TEMPLATE = <<<'EOPHP '
28+ private const REFERENCE_TEMPLATE = <<<'PHP '
2729 <?php
2830
31+ // This file is auto-generated and is for apps only. Bundles SHOULD NOT rely on its content.
32+
2933 namespace Symfony\Component\DependencyInjection\Loader\Configurator;
3034
31- /**
32- * This class provides array-shapes for configuring the services and bundles of an application.
33- *
34- * Services declared with the config() method are autowired and autoconfigured by default.
35- *
36- * All this is auto-generated and is for apps only. Bundles SHOULD NOT rely on it.
37- *
38- * Example:
39- *
40- * ```php
41- * // config/services.php
42- * namespace Symfony\Component\DependencyInjection\Loader\Configurator;
43- *
44- * return App::config([
45- * 'services' => [
46- * 'App\\' => [
47- * 'resource' => '../src/',
48- * ],
49- * ],
50- * ]);
51- * ```
52- *
53- * @psalm-import-type ImportsConfig from AppReference
54- * @psalm-import-type ParametersConfig from AppReference
55- * @psalm-import-type ServicesConfig from AppReference
56- *{APP_TYPES}
57- */
35+ {APP_TYPES}
5836 final class App extends AppReference
5937 {
60- {APP_SHAPE }
38+ {APP_PARAM }
6139 public static function config(array $config): array
6240 {
6341 return parent::config($config);
@@ -66,119 +44,121 @@ public static function config(array $config): array
6644
6745 namespace Symfony\Component\Routing\Loader\Configurator;
6846
69- /**
70- * This class provides array-shapes for configuring the routes of an application.
71- *
72- * All this is auto-generated and is for apps only. Bundles SHOULD NOT rely on it.
73- *
74- * Example:
75- *
76- * ```php
77- * // config/routes.php
78- * namespace Symfony\Component\Routing\Loader\Configurator;
79- *
80- * return Routes::config([
81- * 'controllers' => [
82- * 'resource' => 'attributes',
83- * 'type' => 'tagged_services',
84- * ],
85- * ]);
86- * ```
87- *
88- * @psalm-import-type RouteConfig from RoutesReference
89- * @psalm-import-type ImportConfig from RoutesReference
90- * @psalm-import-type AliasConfig from RoutesReference
91- *
92- * @psalm-type RoutesConfig = array{{ROUTES_SHAPE}
93- * ...<string, ImportConfig|RouteConfig|AliasConfig>
94- * }
95- */
47+ {ROUTES_TYPES}
9648 final class Routes extends RoutesReference
9749 {
98- /**
99- * @param RoutesConfig $config
100- *
101- * @return RoutesConfig
102- */
50+ {ROUTES_PARAM}
10351 public static function config(array $config): array
10452 {
10553 return parent::config($config);
10654 }
10755 }
10856
109- EOPHP;
57+ PHP;
58+
59+ private const WHEN_ENV_APP_TEMPLATE = <<<'PHPDOC'
60+
61+ * "when@{ENV}"?: array{
62+ * imports?: ImportsConfig,
63+ * parameters?: ParametersConfig,
64+ * services?: ServicesConfig,{SHAPE}
65+ * },
66+ PHPDOC;
67+
68+ private const ROUTES_TYPES_TEMPLATE = <<<'PHPDOC'
69+
70+ * @psalm-type RoutesConfig = array{{SHAPE}
71+ * ...<string, RouteConfig|ImportConfig|AliasConfig>
72+ * }
73+ */
74+ PHPDOC;
75+
76+ private const WHEN_ENV_ROUTES_TEMPLATE = <<<'PHPDOC'
77+
78+ * "when@{ENV}"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
79+ PHPDOC;
80+
81+ public function __construct (
82+ private string $ referenceFile ,
83+ private array $ bundlesDefinition ,
84+ ) {
85+ }
11086
11187 public function process (ContainerBuilder $ container ): void
11288 {
113- if (!$ container ->hasParameter ('.kernel.config_dir ' )) {
114- return ;
115- }
116-
117- $ knownEnvs = [];
118- if ($ container ->hasParameter ('.container.known_envs ' )) {
119- $ knownEnvs = array_flip ($ container ->getParameter ('.container.known_envs ' ));
120- }
121- if ($ container ->hasParameter ('.kernel.known_envs ' )) {
122- $ knownEnvs += array_flip ($ container ->getParameter ('.kernel.known_envs ' ));
123- }
124- $ knownEnvs = array_keys ($ knownEnvs );
89+ $ knownEnvs = $ container ->hasParameter ('.container.known_envs ' ) ? $ container ->getParameter ('.container.known_envs ' ) : [$ container ->getParameter ('kernel.environment ' )];
90+ $ knownEnvs = array_unique ($ knownEnvs );
12591 sort ($ knownEnvs );
92+ $ extensionsPerEnv = [];
93+ $ appTypes = '' ;
12694
127- if ($ container ->hasParameter ('.kernel.all_bundles ' )) {
128- $ allBundles = $ container ->getParameter ('.kernel.all_bundles ' );
129- foreach ($ allBundles as $ bundle => $ envs ) {
130- if (!$ extension = (new $ bundle ())->getContainerExtension ()) {
131- continue ;
132- }
133- $ extensions [$ bundle ] = $ extension ;
95+ $ anyEnvExtensions = [];
96+ foreach ($ this ->bundlesDefinition as $ bundle => $ envs ) {
97+ if (!$ extension = (new $ bundle ())->getContainerExtension ()) {
98+ continue ;
13499 }
135- } else {
136- $ extensions = $ container ->getExtensions ();
137- }
100+ if (!$ configuration = $ this ->getConfiguration ($ extension , $ container )) {
101+ continue ;
102+ }
103+ $ anyEnvExtensions [$ bundle ] = $ extension ;
104+ $ type = $ this ->camelCase ($ extension ->getAlias ()).'Config ' ;
105+ $ appTypes .= \sprintf ("\n * @psalm-type %s = %s " , $ type , ArrayShapeGenerator::generate ($ configuration ->getConfigTreeBuilder ()->buildTree ()));
138106
139- $ types = '' ;
140- $ genericShape = '' ;
141- foreach ($ this ->getConfigurations ($ extensions , $ container ) as $ name => $ configuration ) {
142- $ type = $ this ->camelCase ($ name ).'Config ' ;
143- $ types .= \sprintf ("\n * @psalm-type %s = %s " , $ type , ArrayShapeGenerator::generate ($ configuration ->getConfigTreeBuilder ()->buildTree ()));
144- $ genericShape .= \sprintf ("\n %s?: %s, " , $ name , $ type );
107+ foreach ($ knownEnvs as $ env ) {
108+ if ($ envs [$ env ] ?? $ envs ['all ' ] ?? false ) {
109+ $ extensionsPerEnv [$ env ][] = $ extension ;
110+ } else {
111+ unset($ anyEnvExtensions [$ bundle ]);
112+ }
113+ }
145114 }
115+ krsort ($ extensionsPerEnv );
146116
147- $ shape = str_replace ("\n" , "\n * " , $ genericShape );
148- $ phpdoc = (new \ReflectionClass (AppReference::class))->getMethod ('config ' )->getDocComment ();
117+ $ r = new \ReflectionClass (AppReference::class);
149118
150- if ($ phpdoc === $ shape = str_replace ( "\n * ...<string, ExtensionConfig>, " , $ shape , $ phpdoc )) {
151- throw new \LogicException (\sprintf ('Cannot find insertion point for config shape in "%s". ' , AppReference::class));
119+ if (false === $ i = strpos ( $ phpdoc = $ r -> getDocComment (), "\n */ " )) {
120+ throw new \LogicException (\sprintf ('Cannot insert config shape in "%s". ' , AppReference::class));
152121 }
153- $ phpdoc = $ shape ;
154-
155- $ shape = str_replace ("\n" , "\n * " , $ genericShape );
122+ $ appTypes = substr_replace ($ phpdoc , $ appTypes , $ i , 0 );
156123
157- if ($ phpdoc === $ shape = str_replace ("\n * ...<string, ExtensionConfig>, " , $ shape , $ phpdoc )) {
158- throw new \LogicException (\sprintf ('Cannot find insertion point for config shape in "%s". ' , AppReference::class));
124+ if (false === $ i = strpos ($ phpdoc = $ r ->getMethod ('config ' )->getDocComment (), "\n * ...<string, ExtensionType|array{ " )) {
125+ throw new \LogicException (\sprintf ('Cannot insert config shape in "%s". ' , AppReference::class));
126+ }
127+ $ appParam = substr_replace ($ phpdoc , $ this ->getShapeForExtensions ($ anyEnvExtensions , $ container ), $ i , 0 );
128+ $ i += strlen ($ appParam ) - strlen ($ phpdoc );
129+
130+ foreach ($ extensionsPerEnv as $ env => $ extensions ) {
131+ $ appParam = substr_replace ($ appParam , strtr (self ::WHEN_ENV_APP_TEMPLATE , [
132+ '{ENV} ' => $ env ,
133+ '{SHAPE} ' => $ this ->getShapeForExtensions ($ extensions , $ container , ' ' ),
134+ ]), $ i , 0 );
159135 }
160- $ phpdoc = $ shape ;
161- $ routesShape = '' ;
162136
163- if ($ knownEnvs ) {
164- if ($ phpdoc === $ shape = preg_replace ('{\.\.\.<string, array\{ .* when@%env% .*} ' , \sprintf ("...<'when@%s', array{ " , implode ("'|'when@ " , $ knownEnvs )), $ phpdoc )) {
165- throw new \LogicException (\sprintf ('Cannot find insertion point for config shape in "%s". ' , AppReference::class));
166- }
167- $ phpdoc = $ shape ;
137+ $ r = new \ReflectionClass (RoutesReference::class);
168138
169- foreach ($ knownEnvs as $ env ) {
170- $ routesShape .= "\n * 'when@ {$ env }'?: array<string, RouteConfig|ImportConfig|AliasConfig>, " ;
171- }
139+ if (false === $ i = strpos ($ phpdoc = $ r ->getDocComment (), "\n * @psalm-type RoutesConfig = " )) {
140+ throw new \LogicException (\sprintf ('Cannot insert config shape in "%s". ' , RoutesReference::class));
141+ }
142+ $ routesTypes = '' ;
143+ foreach ($ knownEnvs as $ env ) {
144+ $ routesTypes .= strtr (self ::WHEN_ENV_ROUTES_TEMPLATE , ['{ENV} ' => $ env ]);
145+ }
146+ if ('' !== $ routesTypes ) {
147+ $ routesTypes = strtr (self ::ROUTES_TYPES_TEMPLATE , ['{SHAPE} ' => $ routesTypes ]);
148+ $ routesTypes = substr_replace ($ phpdoc , $ routesTypes , $ i );
172149 }
173150
174151 $ configReference = strtr (self ::REFERENCE_TEMPLATE , [
175- '{APP_TYPES} ' => $ types ,
176- '{APP_SHAPE} ' => $ shape ,
177- '{ROUTES_SHAPE} ' => $ routesShape ,
152+ '{APP_TYPES} ' => $ appTypes ,
153+ '{APP_PARAM} ' => $ appParam ,
154+ '{ROUTES_TYPES} ' => $ routesTypes ,
155+ '{ROUTES_PARAM} ' => $ r ->getMethod ('config ' )->getDocComment (),
178156 ]);
179157
180- // Ignore errors when writing to the file
181- @file_put_contents ($ container ->getParameter ('.kernel.config_dir ' ).'/reference.php ' , $ configReference );
158+ $ dir = \dirname ($ this ->referenceFile );
159+ if (is_dir ($ dir ) && is_writable ($ dir ) && (!is_file ($ this ->referenceFile ) || file_get_contents ($ this ->referenceFile ) !== $ configReference )) {
160+ file_put_contents ($ this ->referenceFile , $ configReference );
161+ }
182162 }
183163
184164 private function camelCase (string $ input ): string
@@ -188,19 +168,25 @@ private function camelCase(string $input): string
188168 return preg_replace ('#\W# ' , '' , $ output );
189169 }
190170
191- private function getConfigurations (array $ extensions , ContainerBuilder $ container ): array
171+ private function getConfiguration (ExtensionInterface $ extension , ContainerBuilder $ container ): ?ConfigurationInterface
172+ {
173+ return match (true ) {
174+ $ extension instanceof ConfigurationInterface => $ extension ,
175+ $ extension instanceof ConfigurationExtensionInterface => $ extension ->getConfiguration ([], $ container ),
176+ default => null ,
177+ };
178+ }
179+
180+ private function getShapeForExtensions (array $ extensions , ContainerBuilder $ container , string $ indent = '' ): string
192181 {
193- $ configurations = [] ;
182+ $ shape = '' ;
194183 foreach ($ extensions as $ extension ) {
195- if (null !== $ configuration = match (true ) {
196- $ extension instanceof ConfigurationInterface => $ extension ,
197- $ extension instanceof ConfigurationExtensionInterface => $ extension ->getConfiguration ([], $ container ),
198- default => null ,
199- }) {
200- $ configurations [$ extension ->getAlias ()] = $ configuration ;
184+ if ($ this ->getConfiguration ($ extension , $ container )) {
185+ $ type = $ this ->camelCase ($ extension ->getAlias ()).'Config ' ;
186+ $ shape .= \sprintf ("\n * %s%s?: %s, " , $ indent , $ extension ->getAlias (), $ type );
201187 }
202188 }
203189
204- return $ configurations ;
190+ return $ shape ;
205191 }
206192}
0 commit comments