Configuration database

From MediaWiki.org

Jump to: navigation, search

Contents

[edit] Problems to solve

  • .php config files are fragile
    • easy to break by mistake
    • no edit history unless you explicit do external version control
  • Editing config files is difficult/inconvenient for many sites
    • requires shell access
    • can't easily expose limited settings to admins on the wiki, making serious bottlenecks for site config issues on a big wiki farm
  • Handling config for multi-wiki farms right now is very difficult
    • SiteConfig/InitaliseSettings stuff is weirdly Wikimedia-specific and just plain ugly
    • querying settings from a sister site (even simply translating dbname <-> URL and getting descriptive names) is very difficult, unreliable (various settings may be in CommonSettings and not show up in the config arrays)
    • extensions are difficult to configure since the array is read in before the extensions and their defaults are loaded

[edit] Requirements

  • Should work cleanly for one-off installs
  • Should handle Wikimedia-style farms:
    • global configuration
    • configurations shared among project groups
    • configurations specific to a wiki
    • need to be able to fetch config info from other wikis cleanly
    • Varying user permissions; not all settings should be editable by everyone
  • Metadata...
    • For each config (core or ext) provide:
      • type
      • validation rules
      • friendly name
      • more detailed description, examples
      • Default
  • Needs high-level read/write support, so we can abstract backends more easily (CDB,SQL,etc)

Possibly use HtmlForm or similar framework for defining UI paging? Similar to prefs rewrite?

  • We can adapt some of the form field types in Configure (that's where I got the idea for HTMLForm) for the new configuration (there are some specialist types like group permissions arrays).

[edit] Data types

  • bool
  • int
  • string
    • email
    • URL
    • local path
    • message key
    • other
  • wiki reference
  • array of strings
  • array of items
  •  ?

[edit] Internal interface

Common case: local lookups:

  • Conf::get( 'serverUrl' ) (shortcut)
  • Conf::singleton()->reallyGet( 'serverUrl' )

Other-local-site info lookups:

  • Conf::get( 'serverUrl', 'dewiki' );

abstract class Conf {
	// All of the configuration variables we've loaded thus far
	protected $data;
 
	// The Wiki ID (usually $wgDBname)
	private $wikiId;
 
	// Singleton
	private static $__instance;
 
	protected function __construct( $conf ) {
		$this->config = $conf['id'];
	}
 
	/**
	 * Initialize a new child class based on a configuration array
	 * @param $conf Array of configuration settings, see $wgConfiguration
	 *   for details
	 * @return unknown_type
	 */
	private static function newFromSettings( $conf ) {
		$class = ucfirst( $conf['type'] ) . 'Conf';
		if( !class_exists( $class ) ) {
			throw new MWException( '$wgConf misconfigured with invalid "type"' );
		}
		return new $class( $conf );
	}
 
	/**
	 * Get the singleton if we don't want a specific wiki
	 * @param $wiki String An id for a remote wiki
	 * @return Conf child
	 */
	public static function load( $wiki = false ) {
		if( !self::$__instance ) {
			global $wgConfiguration;
			self::$__instance = self::newFromSettings( $wgConfiguration );
		} if( $wiki && $wiki != self::$__instace->getWikiId() ) {
			// Load configuration for a different wiki, not sure how
			// we're gonna do this yet
			return null;
		}
		return self::$__instance;
	}
 
	/**
	 * Get a property from the configuration database, falling back
	 * to DefaultSettings if undefined
	 * @param $key String Any possible configuration string
	 * @param $wiki String A wiki ID of a remote configuration [no-op]
	 * @return Mixed
	 */
	public static function get( $key, $wiki = false ) {
		return self::load( $wiki )->reallyGet( $key );
	}
 
	/**
	 * Set a property to the configuration database
	 * @param $key String Any possible configuration string
	 * @param $value Mixed Anything
	 * @param $write boolean If true, write to database
	 * @param $wiki String A wiki ID of a remote configuration [no-op]
	 * @return Boolean true on successful write
	 */
	public static function set( $key, $value, $write = false, $wiki = false ) {
		return self::load( $wiki )->reallySet( $key, $value, $write );
	}
 
	/**
	 * What is the wiki ID for this site?
	 * @return String
	 */
	public function getWikiId() {
		return $this->wikiId;
	}
 
	/**
	 * Actually retrieve the data. Check local var cache before
	 * trying the database and then default settings
	 * @param $key String a configuration key
	 * @return Mixed
	 */
	protected function reallyGet( $key ) {
		if( !isset( $this->data[$key] ) ) {
			$val = $this->retrieveSetting( $key );
			if( !$val ) {
				$val = DefaultSettings::$ds[$key];
			}
			$this->data[$key] = $this->stringToBoolean( $val );
		}
		return $this->data[$key];
	}
 
	/**
	 * Set the var to the local cache, then potentially to the database itself
	 * @param $key a configuration key
	 * @param $value Mixed
	 * @param $write Boolean true to write to the database, false for request-only
	 * @return unknown_type
	 */
	protected function reallySet( $key, $value, $write ) {
		if( count( $this->data ) > 100 ) {
			$this->data = array();
		}
		$this->data[$key] = $value;
		if( $write ) {
			return $this->applySetting( $key, $this->booleanToString( $value ) );
		}
		return true;
	}
 
	/**
	 * This is the implementation-specific part of the get() process.
	 * Child classes will need to implement this (eg: SQL queries, DBA
	 * calls, etc)
	 * @param $key String a configuration key
	 * @return Mixed
	 */
	abstract protected function retrieveSetting( $key );
 
	/**
	 * This is the implementation-specific part of the set() process.
	 * Child classes will need to implement this (eg: SQL queries, DBA
	 * calls, etc)
	 * @param $key String a configuration key
	 * @param $value Mixed
	 * @return Boolean success on write
	 */
	abstract protected function applySetting( $key, $value );
 
	/**
	 * Turn special-case 
	 * @return boolean (or original value, if not special-case)
	 */
	private function stringToBoolean( $v ) {
		if( $v === 'boolean-true' ) {
			return true;
		} elseif( $v === 'boolean-false' ) {
			return false;
		}
		return $v;
	}
 
	/**
	 * Munge a boolean into a string
	 * Some databases (like CDB) don't like storing non-strings.
	 * Since booleans are commonly used in configuration, we
	 * munge it to our 'boolean-true|false' strings.
	 * @param $v Mixed, anything to go in the database
	 * @return String (or original value, if not special-case)
	 */
	private function booleanToString( $v ) {
		if( is_boolean( $v ) ) {
			return $v ? 'boolean-true' : 'boolean-false';
		}
		return $v;
	}
}

[edit] User interface

There's a lot that can be adapted or copied from Configure.

[edit] Bugs that could be solved / New features

  • bug 12518 - Cross-wiki userrights reflects groups on local wiki, not target wiki
  • Maybe add a iw_wiki col in the interwiki table that references the wiki ID:
    • bug 11 - Red interwiki links -- check for page existence across wikis
    • bug 9890 - Reasonably efficient interwiki transclusion
      • (these sorts of things benefit from being able to look up things like remote namespaces cleanly, so we can pull data from a db directly instead of doing some nasty api hack)
    • Drop $wgLocalInterwiki and check if iw_wiki == wfWikiId().
  • Maybe run internal HTTP requests (transwiki imports, fetching remote file description page, interwiki transclusion) internally?
  • Drop $wgLocalDatabases and replace this with a query in the database.

[edit] See also