diff options
author | Yaco <franco@reevo.org> | 2020-06-04 11:01:00 -0300 |
---|---|---|
committer | Yaco <franco@reevo.org> | 2020-06-04 11:01:00 -0300 |
commit | fc7369835258467bf97eb64f184b93691f9a9fd5 (patch) | |
tree | daabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/includes/config |
first commit
Diffstat (limited to 'www/wiki/includes/config')
-rw-r--r-- | www/wiki/includes/config/Config.php | 47 | ||||
-rw-r--r-- | www/wiki/includes/config/ConfigException.php | 29 | ||||
-rw-r--r-- | www/wiki/includes/config/ConfigFactory.php | 155 | ||||
-rw-r--r-- | www/wiki/includes/config/EtcdConfig.php | 333 | ||||
-rw-r--r-- | www/wiki/includes/config/EtcdConfigParseError.php | 4 | ||||
-rw-r--r-- | www/wiki/includes/config/GlobalVarConfig.php | 87 | ||||
-rw-r--r-- | www/wiki/includes/config/HashConfig.php | 78 | ||||
-rw-r--r-- | www/wiki/includes/config/MultiConfig.php | 72 | ||||
-rw-r--r-- | www/wiki/includes/config/MutableConfig.php | 38 |
9 files changed, 843 insertions, 0 deletions
diff --git a/www/wiki/includes/config/Config.php b/www/wiki/includes/config/Config.php new file mode 100644 index 00000000..38f589dc --- /dev/null +++ b/www/wiki/includes/config/Config.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Interface for configuration instances + * + * @since 1.23 + */ +interface Config { + + /** + * Get a configuration variable such as "Sitename" or "UploadMaintenance." + * + * @param string $name Name of configuration option + * @return mixed Value configured + * @throws ConfigException + */ + public function get( $name ); + + /** + * Check whether a configuration option is set for the given name + * + * @param string $name Name of configuration option + * @return bool + * @since 1.24 + */ + public function has( $name ); +} diff --git a/www/wiki/includes/config/ConfigException.php b/www/wiki/includes/config/ConfigException.php new file mode 100644 index 00000000..3b3ba9de --- /dev/null +++ b/www/wiki/includes/config/ConfigException.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Exceptions for config failures + * + * @since 1.23 + */ +class ConfigException extends Exception { +} diff --git a/www/wiki/includes/config/ConfigFactory.php b/www/wiki/includes/config/ConfigFactory.php new file mode 100644 index 00000000..2c7afdae --- /dev/null +++ b/www/wiki/includes/config/ConfigFactory.php @@ -0,0 +1,155 @@ +<?php + +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ +use MediaWiki\Services\SalvageableService; +use Wikimedia\Assert\Assert; + +/** + * Factory class to create Config objects + * + * @since 1.23 + */ +class ConfigFactory implements SalvageableService { + + /** + * Map of config name => callback + * @var array + */ + protected $factoryFunctions = []; + + /** + * Config objects that have already been created + * name => Config object + * @var array + */ + protected $configs = []; + + /** + * @deprecated since 1.27, use MediaWikiServices::getConfigFactory() instead. + * + * @return ConfigFactory + */ + public static function getDefaultInstance() { + return \MediaWiki\MediaWikiServices::getInstance()->getConfigFactory(); + } + + /** + * Re-uses existing Cache objects from $other. Cache objects are only re-used if the + * registered factory function for both is the same. Cache config is not copied, + * and only instances of caches defined on this instance with the same config + * are copied. + * + * @see SalvageableService::salvage() + * + * @param SalvageableService $other The object to salvage state from. $other must have the + * exact same type as $this. + */ + public function salvage( SalvageableService $other ) { + Assert::parameterType( self::class, $other, '$other' ); + + /** @var ConfigFactory $other */ + foreach ( $other->factoryFunctions as $name => $otherFunc ) { + if ( !isset( $this->factoryFunctions[$name] ) ) { + continue; + } + + // if the callback function is the same, salvage the Cache object + // XXX: Closures are never equal! + if ( isset( $other->configs[$name] ) + && $this->factoryFunctions[$name] == $otherFunc + ) { + $this->configs[$name] = $other->configs[$name]; + unset( $other->configs[$name] ); + } + } + + // disable $other + $other->factoryFunctions = []; + $other->configs = []; + } + + /** + * @return string[] + */ + public function getConfigNames() { + return array_keys( $this->factoryFunctions ); + } + + /** + * Register a new config factory function. + * Will override if it's already registered. + * Use "*" for $name to provide a fallback config for all unknown names. + * @param string $name + * @param callable|Config $callback A factory callback that takes this ConfigFactory + * as an argument and returns a Config instance, or an existing Config instance. + * @throws InvalidArgumentException If an invalid callback is provided + */ + public function register( $name, $callback ) { + if ( !is_callable( $callback ) && !( $callback instanceof Config ) ) { + if ( is_array( $callback ) ) { + $callback = '[ ' . implode( ', ', $callback ) . ' ]'; + } elseif ( is_object( $callback ) ) { + $callback = 'instanceof ' . get_class( $callback ); + } + throw new InvalidArgumentException( 'Invalid callback \'' . $callback . '\' provided' ); + } + + unset( $this->configs[$name] ); + $this->factoryFunctions[$name] = $callback; + } + + /** + * Create a given Config using the registered callback for $name. + * If an object was already created, the same Config object is returned. + * @param string $name Name of the extension/component you want a Config object for + * 'main' is used for core + * @throws ConfigException If a factory function isn't registered for $name + * @throws UnexpectedValueException If the factory function returns a non-Config object + * @return Config + */ + public function makeConfig( $name ) { + if ( !isset( $this->configs[$name] ) ) { + $key = $name; + if ( !isset( $this->factoryFunctions[$key] ) ) { + $key = '*'; + } + if ( !isset( $this->factoryFunctions[$key] ) ) { + throw new ConfigException( "No registered builder available for $name." ); + } + + if ( $this->factoryFunctions[$key] instanceof Config ) { + $conf = $this->factoryFunctions[$key]; + } else { + $conf = call_user_func( $this->factoryFunctions[$key], $this ); + } + + if ( $conf instanceof Config ) { + $this->configs[$name] = $conf; + } else { + throw new UnexpectedValueException( "The builder for $name returned a non-Config object." ); + } + } + + return $this->configs[$name]; + } + +} diff --git a/www/wiki/includes/config/EtcdConfig.php b/www/wiki/includes/config/EtcdConfig.php new file mode 100644 index 00000000..7020159f --- /dev/null +++ b/www/wiki/includes/config/EtcdConfig.php @@ -0,0 +1,333 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Wikimedia\ObjectFactory; +use Wikimedia\WaitConditionLoop; + +/** + * Interface for configuration instances + * + * @since 1.29 + */ +class EtcdConfig implements Config, LoggerAwareInterface { + /** @var MultiHttpClient */ + private $http; + /** @var BagOStuff */ + private $srvCache; + /** @var array */ + private $procCache; + /** @var LoggerInterface */ + private $logger; + + /** @var string */ + private $host; + /** @var string */ + private $protocol; + /** @var string */ + private $directory; + /** @var string */ + private $encoding; + /** @var int */ + private $baseCacheTTL; + /** @var int */ + private $skewCacheTTL; + /** @var int */ + private $timeout; + + /** + * @param array $params Parameter map: + * - host: the host address and port + * - protocol: either http or https + * - directory: the etc "directory" were MediaWiki specific variables are located + * - encoding: one of ("JSON", "YAML"). Defaults to JSON. [optional] + * - cache: BagOStuff instance or ObjectFactory spec thereof for a server cache. + * The cache will also be used as a fallback if etcd is down. [optional] + * - cacheTTL: logical cache TTL in seconds [optional] + * - skewTTL: maximum seconds to randomly lower the assigned TTL on cache save [optional] + * - timeout: seconds to wait for etcd before throwing an error [optional] + */ + public function __construct( array $params ) { + $params += [ + 'protocol' => 'http', + 'encoding' => 'JSON', + 'cacheTTL' => 10, + 'skewTTL' => 1, + 'timeout' => 2 + ]; + + $this->host = $params['host']; + $this->protocol = $params['protocol']; + $this->directory = trim( $params['directory'], '/' ); + $this->encoding = $params['encoding']; + $this->skewCacheTTL = $params['skewTTL']; + $this->baseCacheTTL = max( $params['cacheTTL'] - $this->skewCacheTTL, 0 ); + $this->timeout = $params['timeout']; + + if ( !isset( $params['cache'] ) ) { + $this->srvCache = new HashBagOStuff(); + } elseif ( $params['cache'] instanceof BagOStuff ) { + $this->srvCache = $params['cache']; + } else { + $this->srvCache = ObjectFactory::getObjectFromSpec( $params['cache'] ); + } + + $this->logger = new Psr\Log\NullLogger(); + $this->http = new MultiHttpClient( [ + 'connTimeout' => $this->timeout, + 'reqTimeout' => $this->timeout, + 'logger' => $this->logger + ] ); + } + + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; + $this->http->setLogger( $logger ); + } + + public function has( $name ) { + $this->load(); + + return array_key_exists( $name, $this->procCache['config'] ); + } + + public function get( $name ) { + $this->load(); + + if ( !array_key_exists( $name, $this->procCache['config'] ) ) { + throw new ConfigException( "No entry found for '$name'." ); + } + + return $this->procCache['config'][$name]; + } + + public function getModifiedIndex() { + $this->load(); + return $this->procCache['modifiedIndex']; + } + + /** + * @throws ConfigException + */ + private function load() { + if ( $this->procCache !== null ) { + return; // already loaded + } + + $now = microtime( true ); + $key = $this->srvCache->makeGlobalKey( + __CLASS__, + $this->host, + $this->directory + ); + + // Get the cached value or block until it is regenerated (by this or another thread)... + $data = null; // latest config info + $error = null; // last error message + $loop = new WaitConditionLoop( + function () use ( $key, $now, &$data, &$error ) { + // Check if the values are in cache yet... + $data = $this->srvCache->get( $key ); + if ( is_array( $data ) && $data['expires'] > $now ) { + $this->logger->debug( "Found up-to-date etcd configuration cache." ); + + return WaitConditionLoop::CONDITION_REACHED; + } + + // Cache is either empty or stale; + // refresh the cache from etcd, using a mutex to reduce stampedes... + if ( $this->srvCache->lock( $key, 0, $this->baseCacheTTL ) ) { + try { + $etcdResponse = $this->fetchAllFromEtcd(); + $error = $etcdResponse['error']; + if ( is_array( $etcdResponse['config'] ) ) { + // Avoid having all servers expire cache keys at the same time + $expiry = microtime( true ) + $this->baseCacheTTL; + $expiry += mt_rand( 0, 1e6 ) / 1e6 * $this->skewCacheTTL; + $data = [ + 'config' => $etcdResponse['config'], + 'expires' => $expiry, + 'modifiedIndex' => $etcdResponse['modifiedIndex'] + ]; + $this->srvCache->set( $key, $data, BagOStuff::TTL_INDEFINITE ); + + $this->logger->info( "Refreshed stale etcd configuration cache." ); + + return WaitConditionLoop::CONDITION_REACHED; + } else { + $this->logger->error( "Failed to fetch configuration: $error" ); + if ( !$etcdResponse['retry'] ) { + // Fail fast since the error is likely to keep happening + return WaitConditionLoop::CONDITION_FAILED; + } + } + } finally { + $this->srvCache->unlock( $key ); // release mutex + } + } + + if ( is_array( $data ) ) { + $this->logger->info( "Using stale etcd configuration cache." ); + + return WaitConditionLoop::CONDITION_REACHED; + } + + return WaitConditionLoop::CONDITION_CONTINUE; + }, + $this->timeout + ); + + if ( $loop->invoke() !== WaitConditionLoop::CONDITION_REACHED ) { + // No cached value exists and etcd query failed; throw an error + throw new ConfigException( "Failed to load configuration from etcd: $error" ); + } + + $this->procCache = $data; + } + + /** + * @return array (containing the keys config, error, retry, modifiedIndex) + */ + public function fetchAllFromEtcd() { + // TODO: inject DnsSrvDiscoverer in order to be able to test this method + $dsd = new DnsSrvDiscoverer( $this->host ); + $servers = $dsd->getServers(); + if ( !$servers ) { + return $this->fetchAllFromEtcdServer( $this->host ); + } + + do { + // Pick a random etcd server from dns + $server = $dsd->pickServer( $servers ); + $host = IP::combineHostAndPort( $server['target'], $server['port'] ); + // Try to load the config from this particular server + $response = $this->fetchAllFromEtcdServer( $host ); + if ( is_array( $response['config'] ) || $response['retry'] ) { + break; + } + + // Avoid the server next time if that failed + $servers = $dsd->removeServer( $server, $servers ); + } while ( $servers ); + + return $response; + } + + /** + * @param string $address Host and port + * @return array (containing the keys config, error, retry, modifiedIndex) + */ + protected function fetchAllFromEtcdServer( $address ) { + // Retrieve all the values under the MediaWiki config directory + list( $rcode, $rdesc, /* $rhdrs */, $rbody, $rerr ) = $this->http->run( [ + 'method' => 'GET', + 'url' => "{$this->protocol}://{$address}/v2/keys/{$this->directory}/?recursive=true", + 'headers' => [ 'content-type' => 'application/json' ] + ] ); + + $response = [ 'config' => null, 'error' => null, 'retry' => false, 'modifiedIndex' => 0 ]; + + static $terminalCodes = [ 404 => true ]; + if ( $rcode < 200 || $rcode > 399 ) { + $response['error'] = strlen( $rerr ) ? $rerr : "HTTP $rcode ($rdesc)"; + $response['retry'] = empty( $terminalCodes[$rcode] ); + return $response; + } + + try { + $parsedResponse = $this->parseResponse( $rbody ); + } catch ( EtcdConfigParseError $e ) { + $parsedResponse = [ 'error' => $e->getMessage() ]; + } + return array_merge( $response, $parsedResponse ); + } + + /** + * Parse a response body, throwing EtcdConfigParseError if there is a validation error + * + * @param string $rbody + * @return array + */ + protected function parseResponse( $rbody ) { + $info = json_decode( $rbody, true ); + if ( $info === null ) { + throw new EtcdConfigParseError( "Error unserializing JSON response." ); + } + if ( !isset( $info['node'] ) || !is_array( $info['node'] ) ) { + throw new EtcdConfigParseError( + "Unexpected JSON response: Missing or invalid node at top level." ); + } + $config = []; + $lastModifiedIndex = $this->parseDirectory( '', $info['node'], $config ); + return [ 'modifiedIndex' => $lastModifiedIndex, 'config' => $config ]; + } + + /** + * Recursively parse a directory node and populate the array passed by + * reference, throwing EtcdConfigParseError if there is a validation error + * + * @param string $dirName The relative directory name + * @param array $dirNode The decoded directory node + * @param array &$config The output array + * @return int lastModifiedIndex The maximum last modified index across all keys in the directory + */ + protected function parseDirectory( $dirName, $dirNode, &$config ) { + $lastModifiedIndex = 0; + if ( !isset( $dirNode['nodes'] ) ) { + throw new EtcdConfigParseError( + "Unexpected JSON response in dir '$dirName'; missing 'nodes' list." ); + } + if ( !is_array( $dirNode['nodes'] ) ) { + throw new EtcdConfigParseError( + "Unexpected JSON response in dir '$dirName'; 'nodes' is not an array." ); + } + + foreach ( $dirNode['nodes'] as $node ) { + $baseName = basename( $node['key'] ); + $fullName = $dirName === '' ? $baseName : "$dirName/$baseName"; + if ( !empty( $node['dir'] ) ) { + $lastModifiedIndex = max( + $this->parseDirectory( $fullName, $node, $config ), + $lastModifiedIndex ); + } else { + $value = $this->unserialize( $node['value'] ); + if ( !is_array( $value ) || !array_key_exists( 'val', $value ) ) { + throw new EtcdConfigParseError( "Failed to parse value for '$fullName'." ); + } + $lastModifiedIndex = max( $node['modifiedIndex'], $lastModifiedIndex ); + $config[$fullName] = $value['val']; + } + } + return $lastModifiedIndex; + } + + /** + * @param string $string + * @return mixed + */ + private function unserialize( $string ) { + if ( $this->encoding === 'YAML' ) { + return yaml_parse( $string ); + } else { // JSON + return json_decode( $string, true ); + } + } +} diff --git a/www/wiki/includes/config/EtcdConfigParseError.php b/www/wiki/includes/config/EtcdConfigParseError.php new file mode 100644 index 00000000..cab90a8e --- /dev/null +++ b/www/wiki/includes/config/EtcdConfigParseError.php @@ -0,0 +1,4 @@ +<?php + +class EtcdConfigParseError extends Exception { +} diff --git a/www/wiki/includes/config/GlobalVarConfig.php b/www/wiki/includes/config/GlobalVarConfig.php new file mode 100644 index 00000000..62953719 --- /dev/null +++ b/www/wiki/includes/config/GlobalVarConfig.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Accesses configuration settings from $GLOBALS + * + * @since 1.23 + */ +class GlobalVarConfig implements Config { + + /** + * Prefix to use for configuration variables + * @var string + */ + private $prefix; + + /** + * Default builder function + * @return GlobalVarConfig + */ + public static function newInstance() { + return new GlobalVarConfig(); + } + + public function __construct( $prefix = 'wg' ) { + $this->prefix = $prefix; + } + + /** + * @inheritDoc + */ + public function get( $name ) { + if ( !$this->has( $name ) ) { + throw new ConfigException( __METHOD__ . ": undefined option: '$name'" ); + } + return $this->getWithPrefix( $this->prefix, $name ); + } + + /** + * @inheritDoc + */ + public function has( $name ) { + return $this->hasWithPrefix( $this->prefix, $name ); + } + + /** + * Get a variable with a given prefix, if not the defaults. + * + * @param string $prefix Prefix to use on the variable, if one. + * @param string $name Variable name without prefix + * @return mixed + */ + protected function getWithPrefix( $prefix, $name ) { + return $GLOBALS[$prefix . $name]; + } + + /** + * Check if a variable with a given prefix is set + * + * @param string $prefix Prefix to use on the variable + * @param string $name Variable name without prefix + * @return bool + */ + protected function hasWithPrefix( $prefix, $name ) { + $var = $prefix . $name; + return array_key_exists( $var, $GLOBALS ); + } +} diff --git a/www/wiki/includes/config/HashConfig.php b/www/wiki/includes/config/HashConfig.php new file mode 100644 index 00000000..d020d20f --- /dev/null +++ b/www/wiki/includes/config/HashConfig.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * A Config instance which stores all settings as a member variable + * + * @since 1.24 + */ +class HashConfig implements Config, MutableConfig { + + /** + * Array of config settings + * + * @var array + */ + private $settings; + + /** + * @return HashConfig + */ + public static function newInstance() { + return new HashConfig; + } + + /** + * @param array $settings Any current settings to pre-load + */ + public function __construct( array $settings = [] ) { + $this->settings = $settings; + } + + /** + * @inheritDoc + */ + public function get( $name ) { + if ( !$this->has( $name ) ) { + throw new ConfigException( __METHOD__ . ": undefined option: '$name'" ); + } + + return $this->settings[$name]; + } + + /** + * @inheritDoc + * @since 1.24 + */ + public function has( $name ) { + return array_key_exists( $name, $this->settings ); + } + + /** + * @see MutableConfig::set + * @param string $name + * @param mixed $value + */ + public function set( $name, $value ) { + $this->settings[$name] = $value; + } +} diff --git a/www/wiki/includes/config/MultiConfig.php b/www/wiki/includes/config/MultiConfig.php new file mode 100644 index 00000000..2bbc84c9 --- /dev/null +++ b/www/wiki/includes/config/MultiConfig.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Provides a fallback sequence for Config objects + * + * @since 1.24 + */ +class MultiConfig implements Config { + + /** + * Array of Config objects to use + * Order matters, the Config objects + * will be checked in order to see + * whether they have the requested setting + * + * @var Config[] + */ + private $configs; + + /** + * @param Config[] $configs + */ + public function __construct( array $configs ) { + $this->configs = $configs; + } + + /** + * @inheritDoc + */ + public function get( $name ) { + foreach ( $this->configs as $config ) { + if ( $config->has( $name ) ) { + return $config->get( $name ); + } + } + + throw new ConfigException( __METHOD__ . ": undefined option: '$name'" ); + } + + /** + * @inheritDoc + */ + public function has( $name ) { + foreach ( $this->configs as $config ) { + if ( $config->has( $name ) ) { + return true; + } + } + + return false; + } +} diff --git a/www/wiki/includes/config/MutableConfig.php b/www/wiki/includes/config/MutableConfig.php new file mode 100644 index 00000000..e765e3bc --- /dev/null +++ b/www/wiki/includes/config/MutableConfig.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Interface for mutable configuration instances + * + * @since 1.24 + */ +interface MutableConfig { + + /** + * Set a configuration variable such a "Sitename" to something like "My Wiki" + * + * @param string $name Name of configuration option + * @param mixed $value Value to set + * @throws ConfigException + */ + public function set( $name, $value ); +} |