Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 187 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
GenerateConfigSchema | |
0.00% |
0 / 187 |
|
0.00% |
0 / 13 |
1806 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
2 | |||
canExecuteWithoutLocalSettings | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDbType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSettings | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
writeOutput | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getOutputPath | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
execute | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
182 | |||
generateSchemaArray | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
20 | |||
generateNames | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
6 | |||
getConstantDeclaration | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
generateSchemaYaml | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
56 | |||
generateVariableStubs | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
getVariableDeclaration | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | use MediaWiki\MainConfigSchema; |
4 | use MediaWiki\Maintenance\Maintenance; |
5 | use MediaWiki\Settings\Config\ConfigSchemaAggregator; |
6 | use MediaWiki\Settings\Source\ReflectionSchemaSource; |
7 | use Symfony\Component\Yaml\Yaml; |
8 | use Wikimedia\StaticArrayWriter; |
9 | |
10 | // @codeCoverageIgnoreStart |
11 | require_once __DIR__ . '/Maintenance.php'; |
12 | |
13 | // Tell Setup.php to load the config schema from MainConfigSchema rather than |
14 | // any generated file, so we can use this script to re-generate a broken schema file. |
15 | define( 'MW_USE_CONFIG_SCHEMA_CLASS', 1 ); |
16 | // @codeCoverageIgnoreEnd |
17 | |
18 | /** |
19 | * Maintenance script that generates configuration schema files: |
20 | * - includes/MainConfigNames.php: name constants for config settings |
21 | * - docs/config-vars.php: dummy variable declarations for config settings |
22 | * - includes/config-schema.php: an optimized config schema for use by Setup.php |
23 | * - docs/config-schema.yaml: a JSON Schema of the config settings |
24 | * |
25 | * @ingroup Maintenance |
26 | */ |
27 | class GenerateConfigSchema extends Maintenance { |
28 | |
29 | private const DEFAULT_NAMES_PATH = __DIR__ . '/../includes/MainConfigNames.php'; |
30 | private const DEFAULT_VARS_PATH = __DIR__ . '/../docs/config-vars.php'; |
31 | private const DEFAULT_ARRAY_PATH = __DIR__ . '/../includes/config-schema.php'; |
32 | private const DEFAULT_SCHEMA_PATH = __DIR__ . '/../docs/config-schema.yaml'; |
33 | private const STDOUT = 'php://stdout'; |
34 | |
35 | /** @var array */ |
36 | private $settingsArray; |
37 | |
38 | public function __construct() { |
39 | parent::__construct(); |
40 | |
41 | $this->addDescription( 'Generates various config schema files.' ); |
42 | |
43 | $this->addOption( |
44 | 'vars', |
45 | 'Path to output variable stubs to. ' . |
46 | 'Default if none of the options is given: ' . |
47 | self::DEFAULT_VARS_PATH, |
48 | false, |
49 | true |
50 | ); |
51 | |
52 | $this->addOption( |
53 | 'schema', |
54 | 'Path to output the schema array to. ' . |
55 | 'Default if none of the options is given: ' . |
56 | self::DEFAULT_ARRAY_PATH, |
57 | false, |
58 | true |
59 | ); |
60 | |
61 | $this->addOption( |
62 | 'names', |
63 | 'Path to output the name constants to. ' . |
64 | 'Default if none of the options is given: ' . |
65 | self::DEFAULT_NAMES_PATH, |
66 | false, |
67 | true |
68 | ); |
69 | |
70 | $this->addOption( |
71 | 'yaml', |
72 | 'Path to output the schema YAML to. ' . |
73 | 'Default if none of the options is given: ' . |
74 | self::DEFAULT_SCHEMA_PATH, |
75 | false, |
76 | true |
77 | ); |
78 | } |
79 | |
80 | public function canExecuteWithoutLocalSettings(): bool { |
81 | return true; |
82 | } |
83 | |
84 | public function getDbType() { |
85 | return self::DB_NONE; |
86 | } |
87 | |
88 | /** |
89 | * Loads the config schema from the MainConfigSchema class. |
90 | * |
91 | * @return array An associative array with a single key, 'config-schema', |
92 | * containing the config schema definition. |
93 | */ |
94 | private function getSettings(): array { |
95 | if ( !$this->settingsArray ) { |
96 | $source = new ReflectionSchemaSource( MainConfigSchema::class, true ); |
97 | $this->settingsArray = $source->load(); |
98 | } |
99 | |
100 | return $this->settingsArray; |
101 | } |
102 | |
103 | /** |
104 | * @param string $path |
105 | * @param string $content |
106 | */ |
107 | private function writeOutput( $path, $content ) { |
108 | // ensure a single line break at the end of the file |
109 | $content = trim( $content ) . "\n"; |
110 | |
111 | file_put_contents( $path, $content ); |
112 | } |
113 | |
114 | /** |
115 | * @param string $name The name of the option |
116 | * |
117 | * @return ?string |
118 | */ |
119 | private function getOutputPath( string $name ): ?string { |
120 | $outputPath = $this->getOption( $name ); |
121 | if ( $outputPath === '-' ) { |
122 | $outputPath = self::STDOUT; |
123 | } |
124 | return $outputPath; |
125 | } |
126 | |
127 | public function execute() { |
128 | $settings = $this->getSettings(); |
129 | $allSchemas = $settings['config-schema']; |
130 | $obsolete = $settings['obsolete-config'] ?? []; |
131 | |
132 | $schemaPath = $this->getOutputPath( 'schema' ); |
133 | $varsPath = $this->getOutputPath( 'vars' ); |
134 | $yamlPath = $this->getOutputPath( 'yaml' ); |
135 | $namesPath = $this->getOutputPath( 'names' ); |
136 | |
137 | if ( $schemaPath === null && $varsPath === null && |
138 | $yamlPath === null && $namesPath === null |
139 | ) { |
140 | // If no output path is specified explicitly, use the default path for all. |
141 | $schemaPath = self::DEFAULT_ARRAY_PATH; |
142 | $varsPath = self::DEFAULT_VARS_PATH; |
143 | $yamlPath = self::DEFAULT_SCHEMA_PATH; |
144 | $namesPath = self::DEFAULT_NAMES_PATH; |
145 | } |
146 | |
147 | if ( $schemaPath === self::STDOUT || $varsPath === self::STDOUT || |
148 | $yamlPath === self::STDOUT || $namesPath === self::STDOUT |
149 | ) { |
150 | // If any of the output is stdout, switch to quiet mode. |
151 | $this->mQuiet = true; |
152 | } |
153 | |
154 | if ( $schemaPath !== null ) { |
155 | $this->output( "Writing schema array to $schemaPath\n" ); |
156 | $this->writeOutput( $schemaPath, $this->generateSchemaArray( $allSchemas, $obsolete ) ); |
157 | } |
158 | |
159 | if ( $varsPath !== null ) { |
160 | $this->output( "Writing variable stubs to $varsPath\n" ); |
161 | $this->writeOutput( $varsPath, $this->generateVariableStubs( $allSchemas ) ); |
162 | } |
163 | |
164 | if ( $yamlPath !== null ) { |
165 | $this->output( "Writing schema YAML to $yamlPath\n" ); |
166 | $this->writeOutput( $yamlPath, $this->generateSchemaYaml( $allSchemas ) ); |
167 | } |
168 | |
169 | if ( $namesPath !== null ) { |
170 | $this->output( "Writing name constants to $namesPath\n" ); |
171 | $this->writeOutput( $namesPath, $this->generateNames( $allSchemas ) ); |
172 | } |
173 | } |
174 | |
175 | public function generateSchemaArray( array $allSchemas, array $obsolete ) { |
176 | $aggregator = new ConfigSchemaAggregator(); |
177 | foreach ( $allSchemas as $key => $schema ) { |
178 | $aggregator->addSchema( $key, $schema ); |
179 | } |
180 | $schemaInverse = [ |
181 | 'default' => $aggregator->getDefaults(), |
182 | 'type' => $aggregator->getTypes(), |
183 | 'mergeStrategy' => $aggregator->getMergeStrategyNames(), |
184 | 'dynamicDefault' => $aggregator->getDynamicDefaults(), |
185 | ]; |
186 | |
187 | $keyMask = array_flip( [ |
188 | 'default', |
189 | 'type', |
190 | 'mergeStrategy', |
191 | 'dynamicDefault', |
192 | 'description', |
193 | 'properties' |
194 | ] ); |
195 | |
196 | $schemaExtra = []; |
197 | foreach ( $aggregator->getDefinedKeys() as $key ) { |
198 | $sch = $aggregator->getSchemaFor( $key ); |
199 | $sch = array_diff_key( $sch, $keyMask ); |
200 | |
201 | if ( $sch ) { |
202 | $schemaExtra[ $key ] = $sch; |
203 | } |
204 | } |
205 | |
206 | $content = ( new StaticArrayWriter() )->write( |
207 | [ |
208 | 'config-schema-inverse' => $schemaInverse, |
209 | 'config-schema' => $schemaExtra, |
210 | 'obsolete-config' => $obsolete |
211 | ], |
212 | "This file is automatically generated using maintenance/generateConfigSchema.php.\n" . |
213 | "Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n" . |
214 | "phpcs:disable Generic.Files.LineLength" |
215 | ); |
216 | |
217 | return $content; |
218 | } |
219 | |
220 | public function generateNames( array $allSchemas ) { |
221 | $code = "<?php\n"; |
222 | $code .= "/**\n" . |
223 | " * This file is automatically generated using maintenance/generateConfigSchema.php.\n" . |
224 | " * Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n" . |
225 | " * @file\n" . |
226 | " * @ingroup Config\n" . |
227 | " */\n\n"; |
228 | |
229 | $code .= "// phpcs:disable Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase\n"; |
230 | $code .= "// phpcs:disable Generic.Files.LineLength.TooLong\n"; |
231 | $code .= "namespace MediaWiki;\n\n"; |
232 | |
233 | $code .= "/**\n" . |
234 | " * A class containing constants representing the names of configuration variables.\n" . |
235 | " * These constants can be used in calls to Config::get() or with ServiceOptions,\n" . |
236 | " * to protect against typos and to make it easier to discover documentation about\n" . |
237 | " * the respective config setting.\n" . |
238 | " *\n" . |
239 | " * @note this class is generated automatically by maintenance/generateConfigSchema.php\n" . |
240 | " * @since 1.39\n" . |
241 | " */\n"; |
242 | |
243 | $code .= "class MainConfigNames {\n"; |
244 | |
245 | // Details about each config variable |
246 | foreach ( $allSchemas as $configKey => $configSchema ) { |
247 | $code .= "\n"; |
248 | $code .= $this->getConstantDeclaration( $configKey, $configSchema ); |
249 | } |
250 | |
251 | $code .= "\n}\n"; |
252 | |
253 | return $code; |
254 | } |
255 | |
256 | /** |
257 | * @param string $name |
258 | * @param array $schema |
259 | * |
260 | * @return string |
261 | */ |
262 | private function getConstantDeclaration( string $name, array $schema ): string { |
263 | $chunks = []; |
264 | |
265 | $chunks[] = "Name constant for the $name setting, for use with Config::get()"; |
266 | $chunks[] = "@see MainConfigSchema::$name"; |
267 | |
268 | if ( isset( $schema['since'] ) ) { |
269 | $chunks[] = "@since {$schema['since']}"; |
270 | } |
271 | |
272 | if ( isset( $schema['deprecated'] ) ) { |
273 | $deprecated = str_replace( "\n", "\n\t * ", wordwrap( $schema['deprecated'] ) ); |
274 | $chunks[] = "@deprecated {$deprecated}"; |
275 | } |
276 | |
277 | $code = "\t/**\n\t * "; |
278 | $code .= implode( "\n\t * ", $chunks ); |
279 | $code .= "\n\t */\n"; |
280 | |
281 | $code .= "\tpublic const $name = '$name';\n"; |
282 | return $code; |
283 | } |
284 | |
285 | public function generateSchemaYaml( array $allSchemas ) { |
286 | foreach ( $allSchemas as &$sch ) { |
287 | // Cast empty arrays to objects if they are declared to be of type object. |
288 | // This ensures they get represented in yaml as {} rather than []. |
289 | if ( isset( $sch['default'] ) && isset( $sch['type'] ) ) { |
290 | $types = (array)$sch['type']; |
291 | if ( $sch['default'] === [] && in_array( 'object', $types ) ) { |
292 | $sch['default'] = new stdClass(); |
293 | } |
294 | } |
295 | |
296 | // Wrap long deprecation messages |
297 | if ( isset( $sch['deprecated'] ) ) { |
298 | $sch['deprecated'] = wordwrap( $sch['deprecated'] ); |
299 | } |
300 | } |
301 | |
302 | // Dynamic defaults are not relevant to yaml consumers |
303 | unset( $sch['dynamicDefault'] ); |
304 | |
305 | $yamlFlags = Yaml::DUMP_OBJECT_AS_MAP |
306 | | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK |
307 | | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE; |
308 | |
309 | $array = [ 'config-schema' => $allSchemas ]; |
310 | $yaml = Yaml::dump( $array, 4, 4, $yamlFlags ); |
311 | |
312 | $header = "# This file is automatically generated using maintenance/generateConfigSchema.php.\n"; |
313 | $header .= "# Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n"; |
314 | |
315 | return $header . $yaml; |
316 | } |
317 | |
318 | public function generateVariableStubs( array $allSchemas ) { |
319 | $content = "<?php\n"; |
320 | $content .= "/**\n" . |
321 | " * This file is automatically generated using maintenance/generateConfigSchema.php.\n" . |
322 | " * Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n" . |
323 | " */\n"; |
324 | |
325 | $content .= "// phpcs:disable\n"; |
326 | $content .= "throw new LogicException( 'Do not load config-vars.php, " . |
327 | "it exists as a documentation stub only' );\n"; |
328 | |
329 | foreach ( $allSchemas as $name => $schema ) { |
330 | $content .= "\n"; |
331 | $content .= $this->getVariableDeclaration( $name, $schema ); |
332 | } |
333 | |
334 | return $content; |
335 | } |
336 | |
337 | /** |
338 | * @param string $name |
339 | * @param array $schema |
340 | * |
341 | * @return string |
342 | */ |
343 | private function getVariableDeclaration( string $name, array $schema ): string { |
344 | $chunks = []; |
345 | $chunks[] = "Config variable stub for the $name setting, for use by phpdoc and IDEs."; |
346 | $chunks[] = "@see MediaWiki\\MainConfigSchema::$name"; |
347 | |
348 | if ( isset( $schema['since'] ) ) { |
349 | $chunks[] = "@since {$schema['since']}"; |
350 | } |
351 | |
352 | if ( isset( $schema['deprecated'] ) ) { |
353 | $deprecated = str_replace( "\n", "\n * ", wordwrap( $schema['deprecated'] ) ); |
354 | $chunks[] = "@deprecated {$deprecated}"; |
355 | } |
356 | |
357 | $code = "/**\n * "; |
358 | $code .= implode( "\n * ", $chunks ); |
359 | $code .= "\n */\n"; |
360 | |
361 | $code .= "\$wg{$name} = null;\n"; |
362 | return $code; |
363 | } |
364 | } |
365 | |
366 | // @codeCoverageIgnoreStart |
367 | $maintClass = GenerateConfigSchema::class; |
368 | require_once RUN_MAINTENANCE_IF_MAIN; |
369 | // @codeCoverageIgnoreEnd |