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