MediaWiki  master
generateConfigSchema.php
Go to the documentation of this file.
1 <?php
2 
6 use Symfony\Component\Yaml\Yaml;
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 
25 
27  private const DEFAULT_NAMES_PATH = __DIR__ . '/../includes/MainConfigNames.php';
28  private const DEFAULT_VARS_PATH = __DIR__ . '/../includes/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 getDbType() {
77  return self::DB_NONE;
78  }
79 
88  private function processTemplate( string $templatePath, string $generatedContent ): string {
89  $oldContent = file_get_contents( $templatePath );
90 
91  // avoid extra line breaks, indentation, etc.
92  $generatedContent = trim( $generatedContent );
93 
94  return preg_replace_callback(
95  '/\{\{[-\w]+\}\}/',
96  static function ( $match ) use ( $generatedContent ) {
97  return $generatedContent;
98  },
99  $oldContent
100  );
101  }
102 
109  private function getSettings(): array {
110  if ( !$this->settingsArray ) {
111  $source = new ReflectionSchemaSource( MainConfigSchema::class, true );
112  $this->settingsArray = $source->load();
113  }
114 
115  return $this->settingsArray;
116  }
117 
122  private function writeOutput( $path, $content ) {
123  // ensure a single line break at the end of the file
124  $content = trim( $content ) . "\n";
125 
126  file_put_contents( $path, $content );
127  }
128 
134  private function getOutputPath( string $name ): ?string {
135  $outputPath = $this->getOption( $name );
136  if ( $outputPath === '-' ) {
137  $outputPath = self::STDOUT;
138  }
139  return $outputPath;
140  }
141 
142  public function execute() {
143  $settings = $this->getSettings();
144  $allSchemas = $settings['config-schema'];
145  $obsolete = $settings['obsolete-config'] ?? [];
146 
147  $schemaPath = $this->getOutputPath( 'schema' );
148  $varsPath = $this->getOutputPath( 'vars' );
149  $yamlPath = $this->getOutputPath( 'yaml' );
150  $namesPath = $this->getOutputPath( 'names' );
151 
152  if ( $schemaPath === null && $varsPath === null &&
153  $yamlPath === null && $namesPath === null
154  ) {
155  // If no output path is specified explicitly, use the default path for all.
156  $schemaPath = self::DEFAULT_ARRAY_PATH;
157  $varsPath = self::DEFAULT_VARS_PATH;
158  $yamlPath = self::DEFAULT_SCHEMA_PATH;
159  $namesPath = self::DEFAULT_NAMES_PATH;
160  }
161 
162  if ( $schemaPath === self::STDOUT || $varsPath === self::STDOUT ||
163  $yamlPath === self::STDOUT || $namesPath === self::STDOUT
164  ) {
165  // If any of the output is stdout, switch to quiet mode.
166  $this->mQuiet = true;
167  }
168 
169  if ( $schemaPath !== null ) {
170  $this->output( "Writing schema array to $schemaPath\n" );
171  $this->writeOutput( $schemaPath, $this->generateSchemaArray( $allSchemas, $obsolete ) );
172  }
173 
174  if ( $varsPath !== null ) {
175  $this->output( "Writing variable stubs to $varsPath\n" );
176  $this->writeOutput( $varsPath, $this->generateVariableStubs( $allSchemas ) );
177  }
178 
179  if ( $yamlPath !== null ) {
180  $this->output( "Writing schema YAML to $yamlPath\n" );
181  $this->writeOutput( $yamlPath, $this->generateSchemaYaml( $allSchemas ) );
182  }
183 
184  if ( $namesPath !== null ) {
185  $this->output( "Writing name constants to $namesPath\n" );
186  $this->writeOutput( $namesPath, $this->generateNames( $allSchemas ) );
187  }
188  }
189 
190  public function generateSchemaArray( array $allSchemas, array $obsolete ) {
191  $aggregator = new ConfigSchemaAggregator();
192  foreach ( $allSchemas as $key => $schema ) {
193  $aggregator->addSchema( $key, $schema );
194  }
195  $schemaInverse = [
196  'default' => $aggregator->getDefaults(),
197  'type' => $aggregator->getTypes(),
198  'mergeStrategy' => $aggregator->getMergeStrategyNames(),
199  'dynamicDefault' => $aggregator->getDynamicDefaults(),
200  ];
201 
202  $keyMask = array_flip( [
203  'default',
204  'type',
205  'mergeStrategy',
206  'dynamicDefault',
207  'description',
208  'properties'
209  ] );
210 
211  $schemaExtra = [];
212  foreach ( $aggregator->getDefinedKeys() as $key ) {
213  $sch = $aggregator->getSchemaFor( $key );
214  $sch = array_diff_key( $sch, $keyMask );
215 
216  if ( $sch ) {
217  $schemaExtra[ $key ] = $sch;
218  }
219  }
220 
221  $content = ( new StaticArrayWriter() )->write(
222  [
223  'config-schema-inverse' => $schemaInverse,
224  'config-schema' => $schemaExtra,
225  'obsolete-config' => $obsolete
226  ],
227  "This file is automatically generated using maintenance/generateConfigSchema.php.\n" .
228  "Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n" .
229  "phpcs:disable Generic.Files.LineLength"
230  );
231 
232  return $content;
233  }
234 
235  public function generateNames( array $allSchemas ) {
236  $code = '';
237 
238  // Details about each config variable
239  foreach ( $allSchemas as $configKey => $configSchema ) {
240  $code .= "\n";
241  $code .= $this->getConstantDeclaration( $configKey, $configSchema );
242  }
243 
244  $newContent =
245  $this->processTemplate( MW_INSTALL_PATH . '/includes/MainConfigNames.template', $code );
246 
247  return $newContent;
248  }
249 
256  private function getConstantDeclaration( string $name, array $schema ): string {
257  $chunks = [];
258 
259  $chunks[] = "Name constant for the $name setting, for use with Config::get()";
260  $chunks[] = "@see MainConfigSchema::$name";
261 
262  if ( isset( $schema['since'] ) ) {
263  $chunks[] = "@since {$schema['since']}";
264  }
265 
266  if ( isset( $schema['deprecated'] ) ) {
267  $deprecated = str_replace( "\n", "\n\t * ", wordwrap( $schema['deprecated'] ) );
268  $chunks[] = "@deprecated {$deprecated}";
269  }
270 
271  $code = "\t/**\n\t * ";
272  $code .= implode( "\n\t * ", $chunks );
273  $code .= "\n\t */\n";
274 
275  $code .= "\tpublic const $name = '$name';\n";
276  return $code;
277  }
278 
279  public function generateSchemaYaml( array $allSchemas ) {
280  foreach ( $allSchemas as &$sch ) {
281  // Cast empty arrays to objects if they are declared to be of type object.
282  // This ensures they get represented in yaml as {} rather than [].
283  if ( isset( $sch['default'] ) && isset( $sch['type'] ) ) {
284  $types = (array)$sch['type'];
285  if ( $sch['default'] === [] && in_array( 'object', $types ) ) {
286  $sch['default'] = new stdClass();
287  }
288  }
289 
290  // Wrap long deprecation messages
291  if ( isset( $sch['deprecated'] ) ) {
292  $sch['deprecated'] = wordwrap( $sch['deprecated'] );
293  }
294  }
295 
296  // Dynamic defaults are not relevant to yaml consumers
297  unset( $sch['dynamicDefault'] );
298 
299  $yamlFlags = Yaml::DUMP_OBJECT_AS_MAP
300  | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK
301  | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE;
302 
303  $array = [ 'config-schema' => $allSchemas ];
304  $yaml = Yaml::dump( $array, 4, 4, $yamlFlags );
305 
306  $header = "# This file is automatically generated using maintenance/generateConfigSchema.php.\n";
307  $header .= "# Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n";
308 
309  return $header . $yaml;
310  }
311 
312  public function generateVariableStubs( array $allSchemas ) {
313  $content = "<?php\n";
314  $content .= "/**\n" .
315  " * This file is automatically generated using maintenance/generateConfigSchema.php.\n" .
316  " * Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n" .
317  " */\n";
318 
319  $content .= "// phpcs:disable\n";
320  $content .= "throw new LogicException( 'Do not load config-vars.php, " .
321  "it exists as a documentation stub only' );\n";
322 
323  foreach ( $allSchemas as $name => $schema ) {
324  $content .= "\n";
325  $content .= $this->getVariableDeclaration( $name, $schema );
326  }
327 
328  return $content;
329  }
330 
337  private function getVariableDeclaration( string $name, array $schema ): string {
338  $chunks = [];
339  $chunks[] = "Config variable stub for the $name setting, for use by phpdoc and IDEs.";
340  $chunks[] = "@see MediaWiki\\MainConfigSchema::$name";
341 
342  if ( isset( $schema['since'] ) ) {
343  $chunks[] = "@since {$schema['since']}";
344  }
345 
346  if ( isset( $schema['deprecated'] ) ) {
347  $deprecated = str_replace( "\n", "\n * ", wordwrap( $schema['deprecated'] ) );
348  $chunks[] = "@deprecated {$deprecated}";
349  }
350 
351  $code = "/**\n * ";
352  $code .= implode( "\n * ", $chunks );
353  $code .= "\n */\n";
354 
355  $code .= "\$wg{$name} = null;\n";
356  return $code;
357  }
358 }
359 
360 $maintClass = GenerateConfigSchema::class;
361 require_once RUN_MAINTENANCE_IF_MAIN;
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition: WebStart.php:82
Maintenance script that generates configuration schema files:
generateVariableStubs(array $allSchemas)
generateSchemaYaml(array $allSchemas)
__construct()
Default constructor.
generateSchemaArray(array $allSchemas, array $obsolete)
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
execute()
Do the actual work.
generateNames(array $allSchemas)
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:66
const DB_NONE
Constants for DB access type.
Definition: Maintenance.php:71
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
This class contains schema declarations for all configuration variables known to MediaWiki core.
Constructs a settings array based on a PHP class by inspecting class members to construct a schema.
Format a static PHP array to be written to a file.
$source
$content
Definition: router.php:76
$header