r51676 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r51675‎ | r51676 | r51677 >
Date:02:41, 10 June 2009
Author:demon
Status:deferred
Tags:
Comment:
Basic implementation of abstract Maintenance class + docs. TODO: Handle config loading (WM-specific and generic), good point to nuke AdminSettings (bug 18768).
Modified paths:
  • /branches/maintenance-work/docs/maintenance.txt (added) (history)
  • /branches/maintenance-work/maintenance/Maintenance.php (added) (history)

Diff [purge]

Index: branches/maintenance-work/maintenance/Maintenance.php
@@ -0,0 +1,376 @@
 2+<?php
 3+
 4+/**
 5+ * Abstract maintenance class for quickly writing and churning out
 6+ * maintenance scripts with minimal effort. All that _must_ be defined
 7+ * is the execute() method. See docs/maintenance.txt for more info
 8+ * and a quick demo of how to use it.
 9+ *
 10+ * @author Chad Horohoe <chad@anyonecanedit.org>
 11+ * @since 1.16
 12+ * @ingroup Maintenance
 13+ */
 14+abstract class Maintenance {
 15+
 16+ // This is the desired params
 17+ protected $mParams = array();
 18+
 19+ // This is the list of options that were actually passed
 20+ protected $mOptions = array();
 21+
 22+ // This is the list of arguments that were actually passed
 23+ protected $mArgs = array();
 24+
 25+ // Name of the script currently running
 26+ protected $mSelf;
 27+
 28+ // Special vars for params that are always used
 29+ private $mQuiet = false;
 30+ private $mDbUser, $mDbPass;
 31+
 32+ // A description of the script, children should change this
 33+ protected $mDescription = '';
 34+
 35+ /**
 36+ * Default constructor. Children should call this if implementing
 37+ * their own constructors
 38+ */
 39+ public function __construct() {
 40+ $this->addDefaultParams();
 41+ }
 42+
 43+ /**
 44+ * Do the actual work. All child classes will need to implement this
 45+ */
 46+ abstract protected function execute();
 47+
 48+ /**
 49+ * Our public interface for doing everything. We'll run some sanity
 50+ * checks and then call the script's execute() method.
 51+ */
 52+ public function go() {
 53+ // Basic stuff to make sure we can go
 54+ $this->sanityChecks();
 55+
 56+ // Begin setting up the environment for script execution
 57+ $this->setupEnvironment();
 58+
 59+ // Get all of the arguments
 60+ $this->loadArgs();
 61+
 62+ // Show the help and die if we can
 63+ $this->maybeHelp();
 64+
 65+ // Load settings, using wikimedia-mode if needed
 66+ if( file_exists( dirname(__FILE__).'/wikimedia-mode' ) ) {
 67+ # TODO FIXME! Wikimedia-specific stuff needs to go away to an ext
 68+ # Maybe a hook?
 69+ $this->loadWikimediaSettings();
 70+ } else {
 71+ $this->loadSettings();
 72+ }
 73+
 74+ // Final setup
 75+ $this->finalSetup();
 76+
 77+ // Execute the actual script
 78+ $this->execute();
 79+ }
 80+
 81+ /**
 82+ * Add a parameter to the script. Will be displayed on --help
 83+ * with the associated description
 84+ *
 85+ * @param $name String The name of the param (help, version, etc)
 86+ * @param $description String The description of the param to show on --help
 87+ * @param $isFlag boolean Whether the param is a flag (like --help) or needs
 88+ * a value (like --dbuser=someuser)
 89+ */
 90+ protected function addParam( $name, $description, $isFlag = false, $required = true ) {
 91+ $this->mParams[ $name ] = array( 'desc' => $description, 'require' => $required );
 92+ }
 93+
 94+ /**
 95+ * Return input from stdin.
 96+ * @param $length int The number of bytes to read
 97+ * @return mixed
 98+ */
 99+ protected function getStdin( $len = 255 ) {
 100+ $f = fopen( 'php://stdin', 'r' );
 101+ $input = fgets( $fr, $len );
 102+ fclose ( $fr );
 103+ return rtrim( $input );
 104+ }
 105+
 106+ /**
 107+ * Throw some output to the user. Scripts can call this with no fears,
 108+ * as we handle all --quiet stuff here
 109+ * @param $out String The text to show to the user
 110+ */
 111+ protected function output( $out ) {
 112+ if( $this->mQuiet ) {
 113+ return;
 114+ }
 115+ $f = fopen( 'php://stdout', 'w' );
 116+ fwrite( $f, $out );
 117+ fclose( $f );
 118+ }
 119+
 120+ /**
 121+ * Throw an error to the user. Doesn't respect --quiet, so don't use
 122+ * this for non-error output
 123+ * @param $err String The error to display
 124+ * @param $die boolean If true, go ahead and die out.
 125+ */
 126+ protected function error( $err, $die = false ) {
 127+ $f = fopen( 'php://stderr', 'w' );
 128+ fwrite( $f, $err );
 129+ fclose( $f );
 130+ if( $die ) die();
 131+ }
 132+
 133+ /**
 134+ * Does the script need DB access? Override this and return true,
 135+ * if needed
 136+ * @return boolean
 137+ */
 138+ protected function needsDB() {
 139+ return false;
 140+ }
 141+
 142+ /**
 143+ * Add the default parameters to the scripts
 144+ */
 145+ private function addDefaultParams() {
 146+ $this->addParam( 'help', "Display this help message", true );
 147+ $this->addParam( 'quiet', "Whether to supress non-error output", true );
 148+ $this->addParam( 'conf', "Location of LocalSettings.php, if not default" );
 149+ $this->addParam( 'aconf', "Same, but for AdminSettings.php" );
 150+ $this->addParam( 'wiki', "For specifying the wiki ID" );
 151+ if( $this->needsDB() ) {
 152+ $this->addParam( 'dbuser', "The DB user to use for this script" );
 153+ $this->addParam( 'dbpass', "The password to use for this script" );
 154+ }
 155+ }
 156+
 157+ /**
 158+ * Do some sanity checking
 159+ */
 160+ private function sanityChecks() {
 161+ # Abort if called from a web server
 162+ if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
 163+ $this->error( "This script must be run from the command line\n", true );
 164+ }
 165+
 166+ # Make sure we can handle script parameters
 167+ if( !ini_get( 'register_argc_argv' ) ) {
 168+ $this->error( "Cannot get command line arguments, register_argc_argv is set to false", true );
 169+ }
 170+
 171+ # Make sure we're on PHP5 or better
 172+ if( version_compare( PHP_VERSION, '5.0.0' ) < 0 ) {
 173+ $this->error( "Sorry! This version of MediaWiki requires PHP 5; you are running " .
 174+ PHP_VERSION . ".\n\n" .
 175+ "If you are sure you already have PHP 5 installed, it may be installed\n" .
 176+ "in a different path from PHP 4. Check with your system administrator.\n", true );
 177+ }
 178+
 179+ if( version_compare( phpversion(), '5.2.4' ) >= 0 ) {
 180+ // Send PHP warnings and errors to stderr instead of stdout.
 181+ // This aids in diagnosing problems, while keeping messages
 182+ // out of redirected output.
 183+ if( ini_get( 'display_errors' ) ) {
 184+ ini_set( 'display_errors', 'stderr' );
 185+ }
 186+
 187+ // Don't touch the setting on earlier versions of PHP,
 188+ // as setting it would disable output if you'd wanted it.
 189+
 190+ // Note that exceptions are also sent to stderr when
 191+ // command-line mode is on, regardless of PHP version.
 192+ }
 193+
 194+ # Set the memory limit
 195+ ini_set( 'memory_limit', -1 );
 196+ }
 197+
 198+ /**
 199+ * Setup the maintenance envinronment
 200+ */
 201+ private function setupEnvironment() {
 202+ global $IP, $wgCommandLineMode, $wgUseNormalUser, $wgRequestTime;
 203+
 204+ $wgRequestTime = microtime(true);
 205+
 206+ # Define us as being in Mediawiki
 207+ define( 'MEDIAWIKI', true );
 208+
 209+ # Setup $IP, using MW_INSTALL_PATH if it exists
 210+ $IP = strval( getenv('MW_INSTALL_PATH') ) !== ''
 211+ ? getenv('MW_INSTALL_PATH')
 212+ : realpath( dirname( __FILE__ ) . '/..' );
 213+
 214+ # Setup the profiler
 215+ if ( file_exists( "$IP/StartProfiler.php" ) ) {
 216+ require_once( "$IP/StartProfiler.php" );
 217+ } else {
 218+ require_once( "$IP/includes/ProfilerStub.php" );
 219+ }
 220+
 221+ $wgCommandLineMode = true;
 222+ # Turn off output buffering if it's on
 223+ @ob_end_flush();
 224+
 225+ if (!isset( $wgUseNormalUser ) ) {
 226+ $wgUseNormalUser = false;
 227+ }
 228+ }
 229+
 230+ /**
 231+ * Process command line arguments
 232+ * $mOptions becomes an array with keys set to the option names
 233+ * $mArgs becomes a zero-based array containing the non-option arguments
 234+ */
 235+ private function loadArgs() {
 236+ global $argv;
 237+ $this->mSelf = array_shift( $argv );
 238+
 239+ $options = array();
 240+ $args = array();
 241+
 242+ # Parse arguments
 243+ for( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
 244+ if ( $arg == '--' ) {
 245+ # End of options, remainder should be considered arguments
 246+ $arg = next( $argv );
 247+ while( $arg !== false ) {
 248+ $args[] = $arg;
 249+ $arg = next( $argv );
 250+ }
 251+ break;
 252+ } elseif ( substr( $arg, 0, 2 ) == '--' ) {
 253+ # Long options
 254+ $option = substr( $arg, 2 );
 255+ if ( isset( $this->mParams[$option] ) ) {
 256+ $param = next( $argv );
 257+ if ( $param === false ) {
 258+ $this->error( "$arg needs a value after it\n", true );
 259+ }
 260+ $options[$option] = $param;
 261+ } else {
 262+ $bits = explode( '=', $option, 2 );
 263+ if( count( $bits ) > 1 ) {
 264+ $option = $bits[0];
 265+ $param = $bits[1];
 266+ } else {
 267+ $param = 1;
 268+ }
 269+ $options[$option] = $param;
 270+ }
 271+ } elseif ( substr( $arg, 0, 1 ) == '-' ) {
 272+ # Short options
 273+ for ( $p=1; $p<strlen( $arg ); $p++ ) {
 274+ $option = $arg{$p};
 275+ if ( isset( $this->mParams[$option] ) ) {
 276+ $param = next( $argv );
 277+ if ( $param === false ) {
 278+ $this->error( "$arg needs a value after it\n", true );
 279+ }
 280+ $options[$option] = $param;
 281+ } else {
 282+ $options[$option] = 1;
 283+ }
 284+ }
 285+ } else {
 286+ $args[] = $arg;
 287+ }
 288+ }
 289+
 290+ # These vars get special treatment
 291+ if( isset( $options['dbuser'] ) )
 292+ $this->mDbUser = $options['dbuser'];
 293+ if( isset( $options['dbpass'] ) )
 294+ $this->mDbPass = $options['dbpass'];
 295+ if( isset( $options['quiet'] ) )
 296+ $this->mQuiet = true;
 297+
 298+ # Check to make sure we've got all the required ones
 299+ foreach( $this->mParams as $opt => $info ) {
 300+ if( $info['required'] && !isset( $this->mOptions[$opt] ) ) {
 301+ $this->error( "Param $opt required.\n", true );
 302+ }
 303+ }
 304+
 305+ $this->mOptions = $options;
 306+ $this->mArgs = $args;
 307+ }
 308+
 309+ private function validateParam(
 310+
 311+ /**
 312+ * Maybe show the help.
 313+ */
 314+ private function maybeHelp() {
 315+ if( isset( $this->mOptions['help'] ) ) {
 316+ $this->mQuiet = false;
 317+ if( $this->mDescription ) {
 318+ $this->output( $this->mDescription . "\n" );
 319+ }
 320+ $this->output( "\nUsage: php " . $this->mSelf . " [--" .
 321+ implode( array_keys( $this->mParams ), "|--" ) . "]\n" );
 322+ foreach( $params as $par => $desc ) {
 323+ $this->output( "\t$par : $desc\n" );
 324+ }
 325+ }
 326+ }
 327+
 328+ /**
 329+ * Handle some last-minute setup here.
 330+ */
 331+ private function finalSetup() {
 332+ global $wgCommandLineMode, $wgUseNormalUser, $wgShowSQLErrors, $wgTitle, $wgProfiling, $IP;
 333+ global $wgDBadminuser, $wgDBadminpassword, $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
 334+
 335+ # Turn off output buffering again, it might have been turned on in the settings files
 336+ if( ob_get_level() ) {
 337+ ob_end_flush();
 338+ }
 339+ # Same with these
 340+ $wgCommandLineMode = true;
 341+
 342+ if ( empty( $wgUseNormalUser ) && isset( $wgDBadminuser ) ) {
 343+ $wgDBuser = $wgDBadminuser;
 344+ $wgDBpassword = $wgDBadminpassword;
 345+
 346+ if( $wgDBservers ) {
 347+ foreach ( $wgDBservers as $i => $server ) {
 348+ $wgDBservers[$i]['user'] = $wgDBuser;
 349+ $wgDBservers[$i]['password'] = $wgDBpassword;
 350+ }
 351+ }
 352+ if( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
 353+ $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
 354+ $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
 355+ }
 356+ }
 357+
 358+ if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
 359+ $fn = MW_CMDLINE_CALLBACK;
 360+ $fn();
 361+ }
 362+
 363+ $wgShowSQLErrors = true;
 364+
 365+ require_once( "$IP/includes/Setup.php" );
 366+ require_once( "$IP/install-utils.inc" );
 367+ $wgTitle = null; # Much much faster startup than creating a title object
 368+ @set_time_limit(0);
 369+
 370+ $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors
 371+ }
 372+
 373+ private function loadWikimediaSettings() {
 374+ }
 375+ private function loadSettings() {
 376+ }
 377+}
Index: branches/maintenance-work/docs/maintenance.txt
@@ -0,0 +1,55 @@
 2+Prior to version 1.16, maintenance scripts were a hodgepodge of code that
 3+had no cohesion or formal method of action. Beginning in 1.16, maintenance
 4+scripts have been cleaned up to use a unified class.
 5+
 6+1. Directory structure
 7+2. How to run a script
 8+3. How to write your own
 9+
 10+1. DIRECTORY STRUCTURE
 11+ The /maintenance directory of a MediaWiki installation contains several
 12+subdirectories, all of which have unique purposes.
 13+
 14+2. HOW TO RUN A SCRIPT
 15+ Ridiculously simple, just call 'php someScript.php' that's in the top-
 16+level /maintenance directory.
 17+
 18+Example:
 19+ php clear_stats.php
 20+
 21+The following parameters are available to all maintenance scripts
 22+--help : Print a help message
 23+--quiet : Quiet non-error output
 24+--dbuser : The database user to use for the script (if needed)
 25+--dbpass : Same as above (if needed)
 26+
 27+3. HOW TO WRITE YOUR OWN
 28+Make a file in the maintenance directory called myScript.php or something.
 29+In it, write the following:
 30+
 31+==BEGIN==
 32+
 33+<?php
 34+
 35+require_once( "Maintenance.php" );
 36+
 37+class DemoMaint extends Maintenance {
 38+
 39+ public function __construct() {
 40+ parent::__construct();
 41+ }
 42+
 43+ protected function execute() {
 44+
 45+ }
 46+}
 47+
 48+$m = new DemoMaint();
 49+$m->go();
 50+
 51+==END==
 52+
 53+That's it. In the execute() method, you have access to all of the normal
 54+MediaWiki functions, so you can get a DB connection, use the cache, etc.
 55+For full docs on the Maintenance class, see the auto-generated docs at
 56+http://svn.wikimedia.org/doc/classMaintenance.html
\ No newline at end of file

Follow-up revisions

RevisionCommit summaryAuthorDate
r52336Merge maintenance-work branch:...demon02:02, 24 June 2009
r53664* (bug 14201) Set $wgDBadminuser/$wgDBadminpassword during setup...demon00:31, 23 July 2009

Status & tagging log