Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/includes/Settings/SettingsBuilder.php
Ðазад
<?php namespace MediaWiki\Settings; use MediaWiki\Config\Config; use MediaWiki\Config\HashConfig; use MediaWiki\Config\IterableConfig; use MediaWiki\HookContainer\HookContainer; use MediaWiki\MainConfigNames; use MediaWiki\Registration\ExtensionRegistry; use MediaWiki\Settings\Cache\CacheableSource; use MediaWiki\Settings\Cache\CachedSource; use MediaWiki\Settings\Config\ConfigBuilder; use MediaWiki\Settings\Config\ConfigSchema; use MediaWiki\Settings\Config\ConfigSchemaAggregator; use MediaWiki\Settings\Config\GlobalConfigBuilder; use MediaWiki\Settings\Config\PhpIniSink; use MediaWiki\Settings\Source\ArraySource; use MediaWiki\Settings\Source\FileSource; use MediaWiki\Settings\Source\SettingsFileUtils; use MediaWiki\Settings\Source\SettingsIncludeLocator; use MediaWiki\Settings\Source\SettingsSource; use RuntimeException; use StatusValue; use Wikimedia\ObjectCache\BagOStuff; use function array_key_exists; /** * Builder class for constructing a Config object from a set of sources * during bootstrap. The SettingsBuilder is used in Setup.php to load * and combine settings files and eventually produce the Config object that * will be used to configure MediaWiki. * * The SettingsBuilder object keeps track of "stages" of initialization that * correspond to sections of Setup.php: * * The initial stage is "loading". In this stage, SettingsSources are added * to the SettingsBuilder using the load* methods. This sets up the config * schema and applies custom configuration values. * * Once all settings sources have been loaded, the SettingsBuilder is moved to the * "registration" stage by calling enterRegistrationStage(). * In this stage, config values may still be altered, but no settings sources may * be loaded. During the "registration" stage, dynamic defaults are applied, * extension registration callbacks are executed, and maintenance scripts have an * opportunity to manipulate settings. * * Finally, the SettingsBuilder is moved to the "operation" stage by calling * enterOperationStage(). This renders the SettingsBuilder read only: config values * may no longer be changed. At this point, it becomes safe to use the Config object * returned by getConfig() to initialize the service container. * * @since 1.38 */ class SettingsBuilder { /** * @var int The initial stage in which settings can be loaded, * but config values cannot be accessed. */ private const STAGE_LOADING = 1; /** * @var int The intermediate stage in which settings can no longer be loaded, * but config values can be accessed and manipulated programmatically. */ private const STAGE_REGISTRATION = 10; /** * @var int The final stage in which config values can be accessed, but can * no longer be changed. */ private const STAGE_READ_ONLY = 100; /** @var string */ private $baseDir; /** @var ExtensionRegistry */ private $extensionRegistry; /** @var BagOStuff */ private $cache; /** @var ConfigBuilder */ private $configSink; /** @var array<string,string> */ private $obsoleteConfig; /** @var Config|null */ private $config; /** @var SettingsSource[] */ private $currentBatch; /** @var ConfigSchemaAggregator */ private $configSchema; /** @var PhpIniSink */ private $phpIniSink; /** * Configuration that applies to SettingsBuilder itself. * Initialized by the constructor, may be overwritten by regular * config values. Merge strategies are currently not implemented * but can be added if needed. * * @var array */ private $settingsConfig; /** * The stage of the settings builder. This is used to determine * which settings are allowed to be changed. * * @var int see self::STAGE_* */ private $stage = self::STAGE_LOADING; /** * Whether we have to apply reverse-merging when applying defaults. * This will initially be false, and become true once any config settings have been * assigned a value. * * This is used as an optimization, to avoid costly merge logic when loading initial * defaults before any config variables have been set. * * @var bool */ private $defaultsNeedMerging = false; /** @var string[] */ private $warnings = []; private static bool $accessDisabledForUnitTests = false; /** * Accessor for the global SettingsBuilder instance. * * @note It is always preferable to have a SettingsBuilder injected! * But as long as we can't to this everywhere, this is the preferred way of * getting the global instance of SettingsBuilder. * * @return SettingsBuilder */ public static function getInstance(): self { static $instance = null; if ( self::$accessDisabledForUnitTests ) { throw new RuntimeException( 'Access is disabled in unit tests' ); } if ( !$instance ) { // NOTE: SettingsBuilder is used during bootstrap, before MediaWikiServices // is available. It has to be, because it is used to construct the // configuration that is used when constructing services. Because of // this, we have to instantiate SettingsBuilder directly, we can't // use service wiring. $instance = new SettingsBuilder( MW_INSTALL_PATH, ExtensionRegistry::getInstance(), new GlobalConfigBuilder( 'wg' ), new PhpIniSink() ); } return $instance; } /** * @internal */ public static function disableAccessForUnitTests(): void { if ( !defined( 'MW_PHPUNIT_TEST' ) ) { throw new RuntimeException( 'Can only be called in tests' ); } self::$accessDisabledForUnitTests = true; } /** * @internal */ public static function enableAccessAfterUnitTests(): void { if ( !defined( 'MW_PHPUNIT_TEST' ) ) { throw new RuntimeException( 'Can only be called in tests' ); } self::$accessDisabledForUnitTests = false; } /** * @param string $baseDir * @param ExtensionRegistry $extensionRegistry * @param ConfigBuilder $configSink * @param PhpIniSink $phpIniSink * @param BagOStuff|null $cache BagOStuff used to cache settings loaded * from each source. The caller should beware that secrets contained in * any source passed to {@link load} or {@link loadFile} will be cached as * well. */ public function __construct( string $baseDir, ExtensionRegistry $extensionRegistry, ConfigBuilder $configSink, PhpIniSink $phpIniSink, ?BagOStuff $cache = null ) { $this->baseDir = $baseDir; $this->extensionRegistry = $extensionRegistry; $this->cache = $cache; $this->configSink = $configSink; $this->obsoleteConfig = []; $this->configSchema = new ConfigSchemaAggregator(); $this->phpIniSink = $phpIniSink; $this->settingsConfig = [ MainConfigNames::ExtensionDirectory => "$baseDir/extensions", MainConfigNames::StyleDirectory => "$baseDir/skins", ]; $this->reset(); } /** * Load settings from a {@link SettingsSource}. * Only allowed during the "loading" stage. * * @param SettingsSource $source * @return $this */ public function load( SettingsSource $source ): self { $this->assertStillLoading( __METHOD__ ); // XXX: We may want to cache the entire batch instead, see T304493. $this->currentBatch[] = $this->wrapSource( $source ); return $this; } /** * Load settings from an array. * * @param array $newSettings * * @return $this */ public function loadArray( array $newSettings ): self { return $this->load( new ArraySource( $newSettings ) ); } /** * Load settings from an array. * For internal use. Allowed during "loading" and "registration" stage. * * @param array $newSettings * @param string $func * * @return $this */ private function loadArrayInternal( array $newSettings, string $func ): self { $this->assertNotReadOnly( $func ); $source = new ArraySource( $newSettings ); $this->currentBatch[] = $this->wrapSource( $source ); return $this; } /** * Load settings from a file. * * @param string $path * @return $this */ public function loadFile( string $path ): self { return $this->load( $this->makeSource( $path ) ); } /** * Checks whether the given file exists relative to the settings builder's * base directory. * * @param string $path * @return bool */ public function fileExists( string $path ): bool { $path = SettingsFileUtils::resolveRelativeLocation( $path, $this->baseDir ); return file_exists( $path ); } /** * @param SettingsSource $source * * @return SettingsSource */ private function wrapSource( SettingsSource $source ): SettingsSource { if ( $this->cache !== null && $source instanceof CacheableSource ) { $source = new CachedSource( $this->cache, $source ); } return $source; } /** * @param string $location * @return SettingsSource */ private function makeSource( $location ): SettingsSource { // NOTE: Currently, files are the only kind of location, but we could add others. // The set of supported source locations will be hard-coded here. // Custom SettingsSource would have to be instantiated directly and passed to load(). $path = SettingsFileUtils::resolveRelativeLocation( $location, $this->baseDir ); return $this->wrapSource( new FileSource( $path ) ); } /** * Assert that the config loaded so far conforms the schema loaded so far. * * @note this is slow, so you probably don't want to do this on every request. * * @return StatusValue */ public function validate(): StatusValue { $config = $this->getConfig(); return $this->configSchema->validateConfig( $config ); } /** * Detect usage of deprecated settings. A setting is counted as used if * it has a value other than the default. Note that deprecated settings are * expected to be supported. Settings that have become non-functional should * be marked as obsolete instead. * * @note this is slow, so you probably don't want to do this on every request. * @note Code that needs to call detectDeprecatedConfig() should probably also * call detectObsoleteConfig() and getWarnings(). * * @return array<string,string> an associative array mapping config keys * to the deprecation messages from the schema. */ public function detectDeprecatedConfig(): array { $config = $this->getConfig(); $keys = $this->getDefinedConfigKeys(); $deprecated = []; foreach ( $keys as $key ) { $sch = $this->configSchema->getSchemaFor( $key ); if ( !isset( $sch['deprecated'] ) ) { continue; } $default = $sch['default'] ?? null; $value = $config->get( $key ); if ( $value !== $default ) { $deprecated[$key] = $sch['deprecated']; } } return $deprecated; } /** * Detect usage of obsolete settings. A setting is counted as used if it is * defined in any way. Note that obsolete settings are non-functional, while * deprecated settings are still supported. * * @note this is slow, so you probably don't want to do this on every request. * @note Code that calls detectObsoleteConfig() may also want to * call detectDeprecatedConfig() and getWarnings(). * * @return array<string,string> an associative array mapping config keys * to the deprecation messages from the schema. */ public function detectObsoleteConfig(): array { $config = $this->getConfig(); $obsolete = []; foreach ( $this->obsoleteConfig as $key => $msg ) { if ( $config->has( $key ) ) { $obsolete[$key] = $msg; } } return $obsolete; } /** * Return a Config object with default for all settings from all schemas loaded so far. * If the schema for a setting doesn't specify a default, null is assumed. * * @note This will implicitly call apply() * * @return IterableConfig */ public function getDefaultConfig(): IterableConfig { $this->apply(); $defaults = $this->configSchema->getDefaults(); $nulls = array_fill_keys( $this->configSchema->getDefinedKeys(), null ); return new HashConfig( array_merge( $nulls, $defaults ) ); } /** * Return the configuration schema. * * @note This will implicitly call apply() * * @return ConfigSchema */ public function getConfigSchema(): ConfigSchema { $this->apply(); return $this->configSchema; } /** * Returns the names of all defined configuration variables * * @return string[] */ public function getDefinedConfigKeys(): array { $this->apply(); return $this->configSchema->getDefinedKeys(); } /** * Apply any settings loaded so far to the runtime environment. * * @note This usually makes all configuration available in global variables. * This may however not be the case in the future. * * @return $this * @throws SettingsBuilderException */ public function apply(): self { if ( !$this->currentBatch ) { return $this; } $this->assertNotReadOnly( __METHOD__ ); $this->config = null; // XXX: We may want to cache the entire batch after merging together // settings from all sources, see T304493. $allSettings = $this->loadRecursive( $this->currentBatch ); foreach ( $allSettings as $settings ) { $this->applySettings( $settings ); } $this->reset(); return $this; } /** * Loads all sources in the current batch, recursively resolving includes. * * @param SettingsSource[] $batch The batch of sources to load * @param string[] $stack The current stack of includes, for cycle detection * * @return array[] an array of settings arrays */ private function loadRecursive( array $batch, array $stack = [] ): array { $allSettings = []; // Depth-first traversal of settings sources. foreach ( $batch as $source ) { $sourceName = (string)$source; if ( in_array( $sourceName, $stack ) ) { throw new SettingsBuilderException( 'Recursive include chain detected: ' . implode( ', ', $stack ) ); } $settings = $source->load(); $settings['source-name'] = $sourceName; $allSettings[] = $settings; $nextBatch = []; foreach ( $settings['includes'] ?? [] as $location ) { // Try to resolve the include relative to the source, // if the source supports that. if ( $source instanceof SettingsIncludeLocator ) { $location = $source->locateInclude( $location ); } $nextBatch[] = $this->makeSource( $location ); } $nextStack = array_merge( $stack, [ $settings['source-name'] ] ); $nextSettings = $this->loadRecursive( $nextBatch, $nextStack ); $allSettings = array_merge( $allSettings, $nextSettings ); } return $allSettings; } /** * Updates config settings relevant to the behavior if SettingsBuilder itself. * * @param array $config * * @return string */ private function updateSettingsConfig( $config ): string { // No merge strategies are applied, defaults are set in the constructor. foreach ( $this->settingsConfig as $key => $dummy ) { if ( array_key_exists( $key, $config ) ) { $this->settingsConfig[ $key ] = $config[ $key ]; } } // @phan-suppress-next-line PhanTypeMismatchReturnNullable,PhanPossiblyUndeclaredVariable Always set return $key; } /** * Notify SettingsBuilder that it can no longer assume that is has full knowledge of * all configuration variables that have been set. This would be the case when other code * (such as LocalSettings.php) is manipulating global variables which represent config * values. * * This is used for optimization: up until this method is called, default values can be set * directly for any config values that have not been set yet. This avoids the need to * run merge logic for all default values during initialization. * * @note It is useful to call apply() just before this method, so any settings already queued * will still benefit from assuming that globals are not dirty. * * @return self */ public function assumeDirtyConfig(): SettingsBuilder { $this->defaultsNeedMerging = true; return $this; } /** * Apply schemas from the settings array. * * This returns the default values to apply, splits into two two categories: * "hard" defaults, which can be applied as config overrides without merging. * And "soft" defaults, which have to be reverse-merged. * Defaults can be considered "hard" if no config value was yet set for them. However, * we can only know that as long as we can be sure that nothing has changed config values * in a way that bypasses SettingsLoader (e.g. by setting global variables in LocalSettings.php). * * @param array $settings A settings structure. */ private function applySchemas( array $settings ) { $defaults = []; if ( isset( $settings['config-schema-inverse'] ) ) { $defaults = $settings['config-schema-inverse']['default'] ?? []; $this->configSchema->addDefaults( $defaults, $settings['source-name'] ); $this->configSchema->addMergeStrategies( $settings['config-schema-inverse']['mergeStrategy'] ?? [], $settings['source-name'] ); $this->configSchema->addTypes( $settings['config-schema-inverse']['type'] ?? [], $settings['source-name'] ); $this->configSchema->addDynamicDefaults( $settings['config-schema-inverse']['dynamicDefault'] ?? [], $settings['source-name'] ); } if ( isset( $settings['config-schema'] ) ) { foreach ( $settings['config-schema'] as $key => $schema ) { $this->configSchema->addSchema( $key, $schema ); if ( $this->configSchema->hasDefaultFor( $key ) ) { $defaults[$key] = $this->configSchema->getDefaultFor( $key ); } } } if ( $this->defaultsNeedMerging ) { $mergeStrategies = $this->configSchema->getMergeStrategies(); $this->configSink->setMultiDefault( $defaults, $mergeStrategies ); } else { // Optimization: no merge strategy, just override in one go $this->configSink->setMulti( $defaults ); } } /** * Apply the settings array. * * @param array $settings */ private function applySettings( array $settings ) { // First extract config variables that change the behavior of SettingsBuilder. // No merge strategies are applied, defaults are set in the constructor. if ( isset( $settings['config'] ) ) { $this->updateSettingsConfig( $settings['config'] ); } if ( isset( $settings['config-overrides'] ) ) { $this->updateSettingsConfig( $settings['config-overrides'] ); } $this->applySchemas( $settings ); if ( isset( $settings['config'] ) ) { $mergeStrategies = $this->configSchema->getMergeStrategies(); $this->configSink->setMulti( $settings['config'], $mergeStrategies ); } if ( isset( $settings['config-overrides'] ) ) { // no merge strategies, just override in one go $this->configSink->setMulti( $settings['config-overrides'] ); } if ( isset( $settings['obsolete-config'] ) ) { $this->obsoleteConfig = array_merge( $this->obsoleteConfig, $settings['obsolete-config'] ); } if ( isset( $settings['config'] ) || isset( $settings['config-overrides'] ) ) { // We have set some config variables, we can no longer assume we can blindly set defaults // without merging with existing config variables. // XXX: We could potentially track which config variables have been set, so we can still // apply defaults for other config vars without merging. $this->defaultsNeedMerging = true; } foreach ( $settings['php-ini'] ?? [] as $option => $value ) { $this->phpIniSink->set( $option, $value ); } // TODO: Closely integrate with ExtensionRegistry. Loading extension.json is basically // the same as loading settings files. See T297166. // That would also mean that extensions would actually be loaded here, // not just queued. We can't do this right now, because we need to preserve // interoperability with wfLoadExtension() being called from LocalSettings.php. if ( isset( $settings['extensions'] ) ) { $extDir = $this->settingsConfig[MainConfigNames::ExtensionDirectory]; foreach ( $settings['extensions'] ?? [] as $ext ) { $path = "$extDir/$ext/extension.json"; // see wfLoadExtension $this->extensionRegistry->queue( $path ); } } if ( isset( $settings['skins'] ) ) { $skinDir = $this->settingsConfig[MainConfigNames::StyleDirectory]; foreach ( $settings['skins'] ?? [] as $skin ) { $path = "$skinDir/$skin/skin.json"; // see wfLoadSkin $this->extensionRegistry->queue( $path ); } } } /** * Puts a value into a config variable. * Depending on the variable's specification, the new value may * be merged with the previous value, or may replace it. * This is a shorthand for putConfigValues( [ $key => $value ] ). * * @see overrideConfigValue * * @param string $key the name of the config setting * @param mixed $value The value to set * * @return $this */ public function putConfigValue( string $key, $value ): self { return $this->putConfigValues( [ $key => $value ] ); } /** * Sets the value of multiple config variables. * Depending on the variables' specification, the new values may * be merged with the previous values, or they may replace them. * This is a shorthand for loadArray( [ 'config' => $values ] ). * * @see overrideConfigValues * * @param array $values An associative array mapping names to values. * * @return $this */ public function putConfigValues( array $values ): self { return $this->loadArrayInternal( [ 'config' => $values ], __METHOD__ ); } /** * Override the value of a config variable. * This ignores any merge strategies and discards any previous value. * This is a shorthand for overrideConfigValues( [ $key => $value ] ). * * @see putConfigValue * * @param string $key the name of the config setting * @param mixed $value The value to set * * @return $this */ public function overrideConfigValue( string $key, $value ): self { return $this->overrideConfigValues( [ $key => $value ] ); } /** * Override the value of multiple config variables. * This ignores any merge strategies and discards any previous value. * This is a shorthand for loadArray( [ 'config-overrides' => $values ] ). * * @see putConfigValues * * @param array $values An associative array mapping names to values. * * @return $this */ public function overrideConfigValues( array $values ): self { return $this->loadArrayInternal( [ 'config-overrides' => $values ], __METHOD__ ); } /** * Register hook handlers. * * @param array<string,mixed> $handlers An associative array using the same structure * as the Hooks config setting: * Each value is a list of handler callbacks for the hook. * * @return $this * @see HookContainer::register() */ public function registerHookHandlers( array $handlers ): self { // NOTE: Rely on the merge strategy for the Hooks setting. // TODO: Make hook handlers a separate structure in settings files, // like they are in extension.json. return $this->loadArrayInternal( [ 'config' => [ 'Hooks' => $handlers ] ], __METHOD__ ); } /** * Register a hook handler. * * @param string $hook * @param mixed $handler * * @return $this * @see HookContainer::register() */ public function registerHookHandler( string $hook, $handler ): self { // NOTE: Rely on the merge strategy for the Hooks setting. // TODO: Make hook handlers a separate structure in settings files, // like they are in extension.json. return $this->loadArray( [ 'config' => [ 'Hooks' => [ $hook => [ $handler ] ] ] ] ); } /** * Returns the config loaded so far. Implicitly triggers apply() when needed. * * @note This will implicitly call apply() * * @return Config */ public function getConfig(): Config { // XXX: Would be nice if we could forbid using this method // before enterRegistrationStage() is called. But we need // access to some configuration earlier, e.g. WikiFarmSettingsDirectory. if ( $this->config && !$this->currentBatch ) { return $this->config; } $this->apply(); $this->config = $this->configSink->build(); return $this->config; } private function reset() { $this->currentBatch = []; } private function assertNotReadOnly( string $func ): void { if ( $this->stage === self::STAGE_READ_ONLY ) { throw new SettingsBuilderException( "$func not supported in operation stage." ); } } private function assertStillLoading( string $func ): void { if ( $this->stage !== self::STAGE_LOADING ) { throw new SettingsBuilderException( "$func only supported while still in the loading stage." ); } } /** * Sets the SettingsBuilder read-only. * * Call this before using the configuration returned by getConfig() to construct services objects * or initialize the service container. * * @internal For use in Setup.php. */ public function enterReadOnlyStage(): void { $this->apply(); $this->stage = self::STAGE_READ_ONLY; } /** * Prevents additional settings from being loaded, but still allows manipulation of config values. * * Call this before applying dynamic defaults and executing extension registration callbacks. * * @internal For use in Setup.php. */ public function enterRegistrationStage(): void { $this->apply(); $this->stage = self::STAGE_REGISTRATION; } /** * @internal For use in Setup.php, pending a better solution. * @return ConfigBuilder */ public function getConfigBuilder(): ConfigBuilder { $this->apply(); return $this->configSink; } /** * Log a settings related warning, such as a deprecated config variable. * * This can be used during bootstrapping, when the regular logger is not yet available. * The warnings will be passed to a regular logger after bootstrapping is complete. * In addition, the updater will fail if it finds any warnings. * This allows us to warn about deprecated settings, and make sure they are * replaced before the update proceeds. * * @param string $msg */ public function warning( string $msg ) { $this->assertNotReadOnly( __METHOD__ ); $this->warnings[] = trim( $msg ); } /** * Returns any warnings logged by calling warning(). * * @internal * @return string[] */ public function getWarnings(): array { return $this->warnings; } }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка