Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Maintenance often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Maintenance, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
52 | abstract class Maintenance { |
||
53 | /** |
||
54 | * Constants for DB access type |
||
55 | * @see Maintenance::getDbType() |
||
56 | */ |
||
57 | const DB_NONE = 0; |
||
58 | const DB_STD = 1; |
||
59 | const DB_ADMIN = 2; |
||
60 | |||
61 | // Const for getStdin() |
||
62 | const STDIN_ALL = 'all'; |
||
63 | |||
64 | // This is the desired params |
||
65 | protected $mParams = []; |
||
66 | |||
67 | // Array of mapping short parameters to long ones |
||
68 | protected $mShortParamsMap = []; |
||
69 | |||
70 | // Array of desired args |
||
71 | protected $mArgList = []; |
||
72 | |||
73 | // This is the list of options that were actually passed |
||
74 | protected $mOptions = []; |
||
75 | |||
76 | // This is the list of arguments that were actually passed |
||
77 | protected $mArgs = []; |
||
78 | |||
79 | // Name of the script currently running |
||
80 | protected $mSelf; |
||
81 | |||
82 | // Special vars for params that are always used |
||
83 | protected $mQuiet = false; |
||
84 | protected $mDbUser, $mDbPass; |
||
85 | |||
86 | // A description of the script, children should change this via addDescription() |
||
87 | protected $mDescription = ''; |
||
88 | |||
89 | // Have we already loaded our user input? |
||
90 | protected $mInputLoaded = false; |
||
91 | |||
92 | /** |
||
93 | * Batch size. If a script supports this, they should set |
||
94 | * a default with setBatchSize() |
||
95 | * |
||
96 | * @var int |
||
97 | */ |
||
98 | protected $mBatchSize = null; |
||
99 | |||
100 | // Generic options added by addDefaultParams() |
||
101 | private $mGenericParameters = []; |
||
102 | // Generic options which might or not be supported by the script |
||
103 | private $mDependantParameters = []; |
||
104 | |||
105 | /** |
||
106 | * Used by getDB() / setDB() |
||
107 | * @var Database |
||
108 | */ |
||
109 | private $mDb = null; |
||
110 | |||
111 | /** @var float UNIX timestamp */ |
||
112 | private $lastReplicationWait = 0.0; |
||
113 | |||
114 | /** |
||
115 | * Used when creating separate schema files. |
||
116 | * @var resource |
||
117 | */ |
||
118 | public $fileHandle; |
||
119 | |||
120 | /** |
||
121 | * Accessible via getConfig() |
||
122 | * |
||
123 | * @var Config |
||
124 | */ |
||
125 | private $config; |
||
126 | |||
127 | /** |
||
128 | * @see Maintenance::requireExtension |
||
129 | * @var array |
||
130 | */ |
||
131 | private $requiredExtensions = []; |
||
132 | |||
133 | /** |
||
134 | * Used to read the options in the order they were passed. |
||
135 | * Useful for option chaining (Ex. dumpBackup.php). It will |
||
136 | * be an empty array if the options are passed in through |
||
137 | * loadParamsAndArgs( $self, $opts, $args ). |
||
138 | * |
||
139 | * This is an array of arrays where |
||
140 | * 0 => the option and 1 => parameter value. |
||
141 | * |
||
142 | * @var array |
||
143 | */ |
||
144 | public $orderedOptions = []; |
||
145 | |||
146 | /** |
||
147 | * Default constructor. Children should call this *first* if implementing |
||
148 | * their own constructors |
||
149 | */ |
||
150 | public function __construct() { |
||
160 | |||
161 | /** |
||
162 | * Should we execute the maintenance script, or just allow it to be included |
||
163 | * as a standalone class? It checks that the call stack only includes this |
||
164 | * function and "requires" (meaning was called from the file scope) |
||
165 | * |
||
166 | * @return bool |
||
167 | */ |
||
168 | public static function shouldExecute() { |
||
193 | |||
194 | /** |
||
195 | * Do the actual work. All child classes will need to implement this |
||
196 | */ |
||
197 | abstract public function execute(); |
||
198 | |||
199 | /** |
||
200 | * Add a parameter to the script. Will be displayed on --help |
||
201 | * with the associated description |
||
202 | * |
||
203 | * @param string $name The name of the param (help, version, etc) |
||
204 | * @param string $description The description of the param to show on --help |
||
205 | * @param bool $required Is the param required? |
||
206 | * @param bool $withArg Is an argument required with this option? |
||
207 | * @param string $shortName Character to use as short name |
||
208 | * @param bool $multiOccurrence Can this option be passed multiple times? |
||
209 | */ |
||
210 | protected function addOption( $name, $description, $required = false, |
||
225 | |||
226 | /** |
||
227 | * Checks to see if a particular param exists. |
||
228 | * @param string $name The name of the param |
||
229 | * @return bool |
||
230 | */ |
||
231 | protected function hasOption( $name ) { |
||
234 | |||
235 | /** |
||
236 | * Get an option, or return the default. |
||
237 | * |
||
238 | * If the option was added to support multiple occurrences, |
||
239 | * this will return an array. |
||
240 | * |
||
241 | * @param string $name The name of the param |
||
242 | * @param mixed $default Anything you want, default null |
||
243 | * @return mixed |
||
244 | */ |
||
245 | protected function getOption( $name, $default = null ) { |
||
255 | |||
256 | /** |
||
257 | * Add some args that are needed |
||
258 | * @param string $arg Name of the arg, like 'start' |
||
259 | * @param string $description Short description of the arg |
||
260 | * @param bool $required Is this required? |
||
261 | */ |
||
262 | protected function addArg( $arg, $description, $required = true ) { |
||
269 | |||
270 | /** |
||
271 | * Remove an option. Useful for removing options that won't be used in your script. |
||
272 | * @param string $name The option to remove. |
||
273 | */ |
||
274 | protected function deleteOption( $name ) { |
||
277 | |||
278 | /** |
||
279 | * Set the description text. |
||
280 | * @param string $text The text of the description |
||
281 | */ |
||
282 | protected function addDescription( $text ) { |
||
285 | |||
286 | /** |
||
287 | * Does a given argument exist? |
||
288 | * @param int $argId The integer value (from zero) for the arg |
||
289 | * @return bool |
||
290 | */ |
||
291 | protected function hasArg( $argId = 0 ) { |
||
294 | |||
295 | /** |
||
296 | * Get an argument. |
||
297 | * @param int $argId The integer value (from zero) for the arg |
||
298 | * @param mixed $default The default if it doesn't exist |
||
299 | * @return mixed |
||
300 | */ |
||
301 | protected function getArg( $argId = 0, $default = null ) { |
||
304 | |||
305 | /** |
||
306 | * Set the batch size. |
||
307 | * @param int $s The number of operations to do in a batch |
||
308 | */ |
||
309 | protected function setBatchSize( $s = 0 ) { |
||
326 | |||
327 | /** |
||
328 | * Get the script's name |
||
329 | * @return string |
||
330 | */ |
||
331 | public function getName() { |
||
334 | |||
335 | /** |
||
336 | * Return input from stdin. |
||
337 | * @param int $len The number of bytes to read. If null, just return the handle. |
||
338 | * Maintenance::STDIN_ALL returns the full length |
||
339 | * @return mixed |
||
340 | */ |
||
341 | protected function getStdin( $len = null ) { |
||
354 | |||
355 | /** |
||
356 | * @return bool |
||
357 | */ |
||
358 | public function isQuiet() { |
||
361 | |||
362 | /** |
||
363 | * Throw some output to the user. Scripts can call this with no fears, |
||
364 | * as we handle all --quiet stuff here |
||
365 | * @param string $out The text to show to the user |
||
366 | * @param mixed $channel Unique identifier for the channel. See function outputChanneled. |
||
367 | */ |
||
368 | protected function output( $out, $channel = null ) { |
||
380 | |||
381 | /** |
||
382 | * Throw an error to the user. Doesn't respect --quiet, so don't use |
||
383 | * this for non-error output |
||
384 | * @param string $err The error to display |
||
385 | * @param int $die If > 0, go ahead and die out using this int as the code |
||
386 | */ |
||
387 | protected function error( $err, $die = 0 ) { |
||
399 | |||
400 | private $atLineStart = true; |
||
401 | private $lastChannel = null; |
||
402 | |||
403 | /** |
||
404 | * Clean up channeled output. Output a newline if necessary. |
||
405 | */ |
||
406 | public function cleanupChanneled() { |
||
412 | |||
413 | /** |
||
414 | * Message outputter with channeled message support. Messages on the |
||
415 | * same channel are concatenated, but any intervening messages in another |
||
416 | * channel start a new line. |
||
417 | * @param string $msg The message without trailing newline |
||
418 | * @param string $channel Channel identifier or null for no |
||
419 | * channel. Channel comparison uses ===. |
||
420 | */ |
||
421 | public function outputChanneled( $msg, $channel = null ) { |
||
443 | |||
444 | /** |
||
445 | * Does the script need different DB access? By default, we give Maintenance |
||
446 | * scripts normal rights to the DB. Sometimes, a script needs admin rights |
||
447 | * access for a reason and sometimes they want no access. Subclasses should |
||
448 | * override and return one of the following values, as needed: |
||
449 | * Maintenance::DB_NONE - For no DB access at all |
||
450 | * Maintenance::DB_STD - For normal DB access, default |
||
451 | * Maintenance::DB_ADMIN - For admin DB access |
||
452 | * @return int |
||
453 | */ |
||
454 | public function getDbType() { |
||
457 | |||
458 | /** |
||
459 | * Add the default parameters to the scripts |
||
460 | */ |
||
461 | protected function addDefaultParams() { |
||
495 | |||
496 | /** |
||
497 | * @since 1.24 |
||
498 | * @return Config |
||
499 | */ |
||
500 | View Code Duplication | public function getConfig() { |
|
507 | |||
508 | /** |
||
509 | * @since 1.24 |
||
510 | * @param Config $config |
||
511 | */ |
||
512 | public function setConfig( Config $config ) { |
||
515 | |||
516 | /** |
||
517 | * Indicate that the specified extension must be |
||
518 | * loaded before the script can run. |
||
519 | * |
||
520 | * This *must* be called in the constructor. |
||
521 | * |
||
522 | * @since 1.28 |
||
523 | * @param string $name |
||
524 | */ |
||
525 | protected function requireExtension( $name ) { |
||
528 | |||
529 | /** |
||
530 | * Verify that the required extensions are installed |
||
531 | * |
||
532 | * @since 1.28 |
||
533 | */ |
||
534 | public function checkRequiredExtensions() { |
||
551 | |||
552 | /** |
||
553 | * Set triggers like when to try to run deferred updates |
||
554 | * @since 1.28 |
||
555 | */ |
||
556 | public function setAgentAndTriggers() { |
||
571 | |||
572 | /** |
||
573 | * @param LBFactory $LBFactory |
||
574 | * @since 1.28 |
||
575 | */ |
||
576 | public static function setLBFactoryTriggers( LBFactory $LBFactory ) { |
||
602 | |||
603 | /** |
||
604 | * Run a child maintenance script. Pass all of the current arguments |
||
605 | * to it. |
||
606 | * @param string $maintClass A name of a child maintenance class |
||
607 | * @param string $classFile Full path of where the child is |
||
608 | * @return Maintenance |
||
609 | */ |
||
610 | public function runChild( $maintClass, $classFile = null ) { |
||
632 | |||
633 | /** |
||
634 | * Do some sanity checking and basic setup |
||
635 | */ |
||
636 | public function setup() { |
||
687 | |||
688 | /** |
||
689 | * Normally we disable the memory_limit when running admin scripts. |
||
690 | * Some scripts may wish to actually set a limit, however, to avoid |
||
691 | * blowing up unexpectedly. We also support a --memory-limit option, |
||
692 | * to allow sysadmins to explicitly set one if they'd prefer to override |
||
693 | * defaults (or for people using Suhosin which yells at you for trying |
||
694 | * to disable the limits) |
||
695 | * @return string |
||
696 | */ |
||
697 | public function memoryLimit() { |
||
702 | |||
703 | /** |
||
704 | * Adjusts PHP's memory limit to better suit our needs, if needed. |
||
705 | */ |
||
706 | protected function adjustMemoryLimit() { |
||
715 | |||
716 | /** |
||
717 | * Activate the profiler (assuming $wgProfiler is set) |
||
718 | */ |
||
719 | protected function activateProfiler() { |
||
743 | |||
744 | /** |
||
745 | * Clear all params and arguments. |
||
746 | */ |
||
747 | public function clearParamsAndArgs() { |
||
752 | |||
753 | /** |
||
754 | * Load params and arguments from a given array |
||
755 | * of command-line arguments |
||
756 | * |
||
757 | * @since 1.27 |
||
758 | * @param array $argv |
||
759 | */ |
||
760 | public function loadWithArgv( $argv ) { |
||
830 | |||
831 | /** |
||
832 | * Helper function used solely by loadParamsAndArgs |
||
833 | * to prevent code duplication |
||
834 | * |
||
835 | * This sets the param in the options array based on |
||
836 | * whether or not it can be specified multiple times. |
||
837 | * |
||
838 | * @since 1.27 |
||
839 | * @param array $options |
||
840 | * @param string $option |
||
841 | * @param mixed $value |
||
842 | */ |
||
843 | private function setParam( &$options, $option, $value ) { |
||
863 | |||
864 | /** |
||
865 | * Process command line arguments |
||
866 | * $mOptions becomes an array with keys set to the option names |
||
867 | * $mArgs becomes a zero-based array containing the non-option arguments |
||
868 | * |
||
869 | * @param string $self The name of the script, if any |
||
870 | * @param array $opts An array of options, in form of key=>value |
||
871 | * @param array $args An array of command line arguments |
||
872 | */ |
||
873 | public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) { |
||
901 | |||
902 | /** |
||
903 | * Run some validation checks on the params, etc |
||
904 | */ |
||
905 | protected function validateParamsAndArgs() { |
||
926 | |||
927 | /** |
||
928 | * Handle the special variables that are global to all scripts |
||
929 | */ |
||
930 | protected function loadSpecialVars() { |
||
944 | |||
945 | /** |
||
946 | * Maybe show the help. |
||
947 | * @param bool $force Whether to force the help to show, default false |
||
948 | */ |
||
949 | protected function maybeHelp( $force = false ) { |
||
1060 | |||
1061 | /** |
||
1062 | * Handle some last-minute setup here. |
||
1063 | */ |
||
1064 | public function finalSetup() { |
||
1122 | |||
1123 | /** |
||
1124 | * Execute a callback function at the end of initialisation |
||
1125 | */ |
||
1126 | protected function afterFinalSetup() { |
||
1131 | |||
1132 | /** |
||
1133 | * Potentially debug globals. Originally a feature only |
||
1134 | * for refreshLinks |
||
1135 | */ |
||
1136 | public function globals() { |
||
1141 | |||
1142 | /** |
||
1143 | * Generic setup for most installs. Returns the location of LocalSettings |
||
1144 | * @return string |
||
1145 | */ |
||
1146 | public function loadSettings() { |
||
1174 | |||
1175 | /** |
||
1176 | * Support function for cleaning up redundant text records |
||
1177 | * @param bool $delete Whether or not to actually delete the records |
||
1178 | * @author Rob Church <[email protected]> |
||
1179 | */ |
||
1180 | public function purgeRedundantText( $delete = true ) { |
||
1229 | |||
1230 | /** |
||
1231 | * Get the maintenance directory. |
||
1232 | * @return string |
||
1233 | */ |
||
1234 | protected function getDir() { |
||
1237 | |||
1238 | /** |
||
1239 | * Returns a database to be used by current maintenance script. It can be set by setDB(). |
||
1240 | * If not set, wfGetDB() will be used. |
||
1241 | * This function has the same parameters as wfGetDB() |
||
1242 | * |
||
1243 | * @param integer $db DB index (DB_REPLICA/DB_MASTER) |
||
1244 | * @param array $groups; default: empty array |
||
1245 | * @param string|bool $wiki; default: current wiki |
||
1246 | * @return Database |
||
1247 | */ |
||
1248 | protected function getDB( $db, $groups = [], $wiki = false ) { |
||
1255 | |||
1256 | /** |
||
1257 | * Sets database object to be returned by getDB(). |
||
1258 | * |
||
1259 | * @param IDatabase $db Database object to be used |
||
1260 | */ |
||
1261 | public function setDB( IDatabase $db ) { |
||
1264 | |||
1265 | /** |
||
1266 | * Begin a transcation on a DB |
||
1267 | * |
||
1268 | * This method makes it clear that begin() is called from a maintenance script, |
||
1269 | * which has outermost scope. This is safe, unlike $dbw->begin() called in other places. |
||
1270 | * |
||
1271 | * @param IDatabase $dbw |
||
1272 | * @param string $fname Caller name |
||
1273 | * @since 1.27 |
||
1274 | */ |
||
1275 | protected function beginTransaction( IDatabase $dbw, $fname ) { |
||
1278 | |||
1279 | /** |
||
1280 | * Commit the transcation on a DB handle and wait for replica DBs to catch up |
||
1281 | * |
||
1282 | * This method makes it clear that commit() is called from a maintenance script, |
||
1283 | * which has outermost scope. This is safe, unlike $dbw->commit() called in other places. |
||
1284 | * |
||
1285 | * @param IDatabase $dbw |
||
1286 | * @param string $fname Caller name |
||
1287 | * @return bool Whether the replica DB wait succeeded |
||
1288 | * @since 1.27 |
||
1289 | */ |
||
1290 | protected function commitTransaction( IDatabase $dbw, $fname ) { |
||
1304 | |||
1305 | /** |
||
1306 | * Rollback the transcation on a DB handle |
||
1307 | * |
||
1308 | * This method makes it clear that rollback() is called from a maintenance script, |
||
1309 | * which has outermost scope. This is safe, unlike $dbw->rollback() called in other places. |
||
1310 | * |
||
1311 | * @param IDatabase $dbw |
||
1312 | * @param string $fname Caller name |
||
1313 | * @since 1.27 |
||
1314 | */ |
||
1315 | protected function rollbackTransaction( IDatabase $dbw, $fname ) { |
||
1318 | |||
1319 | /** |
||
1320 | * Lock the search index |
||
1321 | * @param Database &$db |
||
1322 | */ |
||
1323 | private function lockSearchindex( $db ) { |
||
1336 | |||
1337 | /** |
||
1338 | * Unlock the tables |
||
1339 | * @param Database &$db |
||
1340 | */ |
||
1341 | private function unlockSearchindex( $db ) { |
||
1344 | |||
1345 | /** |
||
1346 | * Unlock and lock again |
||
1347 | * Since the lock is low-priority, queued reads will be able to complete |
||
1348 | * @param Database &$db |
||
1349 | */ |
||
1350 | private function relockSearchindex( $db ) { |
||
1354 | |||
1355 | /** |
||
1356 | * Perform a search index update with locking |
||
1357 | * @param int $maxLockTime The maximum time to keep the search index locked. |
||
1358 | * @param string $callback The function that will update the function. |
||
1359 | * @param Database $dbw |
||
1360 | * @param array $results |
||
1361 | */ |
||
1362 | public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) { |
||
1392 | |||
1393 | /** |
||
1394 | * Update the searchindex table for a given pageid |
||
1395 | * @param Database $dbw A database write handle |
||
1396 | * @param int $pageId The page ID to update. |
||
1397 | * @return null|string |
||
1398 | */ |
||
1399 | public function updateSearchIndexForPage( $dbw, $pageId ) { |
||
1415 | |||
1416 | /** |
||
1417 | * Wrapper for posix_isatty() |
||
1418 | * We default as considering stdin a tty (for nice readline methods) |
||
1419 | * but treating stout as not a tty to avoid color codes |
||
1420 | * |
||
1421 | * @param mixed $fd File descriptor |
||
1422 | * @return bool |
||
1423 | */ |
||
1424 | public static function posix_isatty( $fd ) { |
||
1431 | |||
1432 | /** |
||
1433 | * Prompt the console for input |
||
1434 | * @param string $prompt What to begin the line with, like '> ' |
||
1435 | * @return string Response |
||
1436 | */ |
||
1437 | public static function readconsole( $prompt = '> ' ) { |
||
1469 | |||
1470 | /** |
||
1471 | * Emulate readline() |
||
1472 | * @param string $prompt What to begin the line with, like '> ' |
||
1473 | * @return string |
||
1474 | */ |
||
1475 | private static function readlineEmulation( $prompt ) { |
||
1504 | |||
1505 | /** |
||
1506 | * Get the terminal size as a two-element array where the first element |
||
1507 | * is the width (number of columns) and the second element is the height |
||
1508 | * (number of rows). |
||
1509 | * |
||
1510 | * @return array |
||
1511 | */ |
||
1512 | public static function getTermSize() { |
||
1534 | |||
1535 | /** |
||
1536 | * Call this to set up the autoloader to allow classes to be used from the |
||
1537 | * tests directory. |
||
1538 | */ |
||
1539 | public static function requireTestsAutoloader() { |
||
1542 | } |
||
1543 | |||
1624 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.