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 RequestContext 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 RequestContext, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 33 | class RequestContext implements IContextSource, MutableContext { |
||
| 34 | /** |
||
| 35 | * @var WebRequest |
||
| 36 | */ |
||
| 37 | private $request; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * @var Title |
||
| 41 | */ |
||
| 42 | private $title; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * @var WikiPage |
||
| 46 | */ |
||
| 47 | private $wikipage; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * @var OutputPage |
||
| 51 | */ |
||
| 52 | private $output; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @var User |
||
| 56 | */ |
||
| 57 | private $user; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @var Language |
||
| 61 | */ |
||
| 62 | private $lang; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * @var Skin |
||
| 66 | */ |
||
| 67 | private $skin; |
||
| 68 | |||
| 69 | /** |
||
| 70 | * @var Timing |
||
| 71 | */ |
||
| 72 | private $timing; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * @var Config |
||
| 76 | */ |
||
| 77 | private $config; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * @var RequestContext |
||
| 81 | */ |
||
| 82 | private static $instance = null; |
||
| 83 | |||
| 84 | /** |
||
| 85 | * Set the Config object |
||
| 86 | * |
||
| 87 | * @param Config $c |
||
| 88 | */ |
||
| 89 | public function setConfig( Config $c ) { |
||
| 92 | |||
| 93 | /** |
||
| 94 | * Get the Config object |
||
| 95 | * |
||
| 96 | * @return Config |
||
| 97 | */ |
||
| 98 | public function getConfig() { |
||
| 107 | |||
| 108 | /** |
||
| 109 | * Set the WebRequest object |
||
| 110 | * |
||
| 111 | * @param WebRequest $r |
||
| 112 | */ |
||
| 113 | public function setRequest( WebRequest $r ) { |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Get the WebRequest object |
||
| 119 | * |
||
| 120 | * @return WebRequest |
||
| 121 | */ |
||
| 122 | public function getRequest() { |
||
| 135 | |||
| 136 | /** |
||
| 137 | * Get the Stats object |
||
| 138 | * |
||
| 139 | * @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) |
||
| 140 | * |
||
| 141 | * @return StatsdDataFactory |
||
| 142 | */ |
||
| 143 | public function getStats() { |
||
| 146 | |||
| 147 | /** |
||
| 148 | * Get the timing object |
||
| 149 | * |
||
| 150 | * @return Timing |
||
| 151 | */ |
||
| 152 | public function getTiming() { |
||
| 160 | |||
| 161 | /** |
||
| 162 | * Set the Title object |
||
| 163 | * |
||
| 164 | * @param Title|null $title |
||
| 165 | */ |
||
| 166 | public function setTitle( Title $title = null ) { |
||
| 171 | |||
| 172 | /** |
||
| 173 | * Get the Title object |
||
| 174 | * |
||
| 175 | * @return Title|null |
||
| 176 | */ |
||
| 177 | public function getTitle() { |
||
| 189 | |||
| 190 | /** |
||
| 191 | * Check, if a Title object is set |
||
| 192 | * |
||
| 193 | * @since 1.25 |
||
| 194 | * @return bool |
||
| 195 | */ |
||
| 196 | public function hasTitle() { |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Check whether a WikiPage object can be get with getWikiPage(). |
||
| 202 | * Callers should expect that an exception is thrown from getWikiPage() |
||
| 203 | * if this method returns false. |
||
| 204 | * |
||
| 205 | * @since 1.19 |
||
| 206 | * @return bool |
||
| 207 | */ |
||
| 208 | public function canUseWikiPage() { |
||
| 218 | |||
| 219 | /** |
||
| 220 | * Set the WikiPage object |
||
| 221 | * |
||
| 222 | * @since 1.19 |
||
| 223 | * @param WikiPage $p |
||
| 224 | */ |
||
| 225 | public function setWikiPage( WikiPage $p ) { |
||
| 233 | |||
| 234 | /** |
||
| 235 | * Get the WikiPage object. |
||
| 236 | * May throw an exception if there's no Title object set or the Title object |
||
| 237 | * belongs to a special namespace that doesn't have WikiPage, so use first |
||
| 238 | * canUseWikiPage() to check whether this method can be called safely. |
||
| 239 | * |
||
| 240 | * @since 1.19 |
||
| 241 | * @throws MWException |
||
| 242 | * @return WikiPage |
||
| 243 | */ |
||
| 244 | public function getWikiPage() { |
||
| 255 | |||
| 256 | /** |
||
| 257 | * @param OutputPage $o |
||
| 258 | */ |
||
| 259 | public function setOutput( OutputPage $o ) { |
||
| 262 | |||
| 263 | /** |
||
| 264 | * Get the OutputPage object |
||
| 265 | * |
||
| 266 | * @return OutputPage |
||
| 267 | */ |
||
| 268 | public function getOutput() { |
||
| 275 | |||
| 276 | /** |
||
| 277 | * Set the User object |
||
| 278 | * |
||
| 279 | * @param User $u |
||
| 280 | */ |
||
| 281 | public function setUser( User $u ) { |
||
| 284 | |||
| 285 | /** |
||
| 286 | * Get the User object |
||
| 287 | * |
||
| 288 | * @return User |
||
| 289 | */ |
||
| 290 | public function getUser() { |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Accepts a language code and ensures it's sane. Outputs a cleaned up language |
||
| 300 | * code and replaces with $wgLanguageCode if not sane. |
||
| 301 | * @param string $code Language code |
||
| 302 | * @return string |
||
| 303 | */ |
||
| 304 | public static function sanitizeLangCode( $code ) { |
||
| 318 | |||
| 319 | /** |
||
| 320 | * Set the Language object |
||
| 321 | * |
||
| 322 | * @param Language|string $l Language instance or language code |
||
| 323 | * @throws MWException |
||
| 324 | * @since 1.19 |
||
| 325 | */ |
||
| 326 | View Code Duplication | public function setLanguage( $l ) { |
|
| 337 | |||
| 338 | /** |
||
| 339 | * Get the Language object. |
||
| 340 | * Initialization of user or request objects can depend on this. |
||
| 341 | * @return Language |
||
| 342 | * @throws Exception |
||
| 343 | * @since 1.19 |
||
| 344 | */ |
||
| 345 | public function getLanguage() { |
||
| 387 | |||
| 388 | /** |
||
| 389 | * Set the Skin object |
||
| 390 | * |
||
| 391 | * @param Skin $s |
||
| 392 | */ |
||
| 393 | public function setSkin( Skin $s ) { |
||
| 397 | |||
| 398 | /** |
||
| 399 | * Get the Skin object |
||
| 400 | * |
||
| 401 | * @return Skin |
||
| 402 | */ |
||
| 403 | public function getSkin() { |
||
| 445 | |||
| 446 | /** Helpful methods **/ |
||
| 447 | |||
| 448 | /** |
||
| 449 | * Get a Message object with context set |
||
| 450 | * Parameters are the same as wfMessage() |
||
| 451 | * |
||
| 452 | * @param mixed ... |
||
| 453 | * @return Message |
||
| 454 | */ |
||
| 455 | public function msg() { |
||
| 460 | |||
| 461 | /** Static methods **/ |
||
| 462 | |||
| 463 | /** |
||
| 464 | * Get the RequestContext object associated with the main request |
||
| 465 | * |
||
| 466 | * @return RequestContext |
||
| 467 | */ |
||
| 468 | public static function getMain() { |
||
| 475 | |||
| 476 | /** |
||
| 477 | * Get the RequestContext object associated with the main request |
||
| 478 | * and gives a warning to the log, to find places, where a context maybe is missing. |
||
| 479 | * |
||
| 480 | * @param string $func |
||
| 481 | * @return RequestContext |
||
| 482 | * @since 1.24 |
||
| 483 | */ |
||
| 484 | public static function getMainAndWarn( $func = __METHOD__ ) { |
||
| 490 | |||
| 491 | /** |
||
| 492 | * Resets singleton returned by getMain(). Should be called only from unit tests. |
||
| 493 | */ |
||
| 494 | public static function resetMain() { |
||
| 500 | |||
| 501 | /** |
||
| 502 | * Export the resolved user IP, HTTP headers, user ID, and session ID. |
||
| 503 | * The result will be reasonably sized to allow for serialization. |
||
| 504 | * |
||
| 505 | * @return array |
||
| 506 | * @since 1.21 |
||
| 507 | */ |
||
| 508 | public function exportSession() { |
||
| 517 | |||
| 518 | /** |
||
| 519 | * Import an client IP address, HTTP headers, user ID, and session ID |
||
| 520 | * |
||
| 521 | * This sets the current session, $wgUser, and $wgRequest from $params. |
||
| 522 | * Once the return value falls out of scope, the old context is restored. |
||
| 523 | * This method should only be called in contexts where there is no session |
||
| 524 | * ID or end user receiving the response (CLI or HTTP job runners). This |
||
| 525 | * is partly enforced, and is done so to avoid leaking cookies if certain |
||
| 526 | * error conditions arise. |
||
| 527 | * |
||
| 528 | * This is useful when background scripts inherit context when acting on |
||
| 529 | * behalf of a user. In general the 'sessionId' parameter should be set |
||
| 530 | * to an empty string unless session importing is *truly* needed. This |
||
| 531 | * feature is somewhat deprecated. |
||
| 532 | * |
||
| 533 | * @note suhosin.session.encrypt may interfere with this method. |
||
| 534 | * |
||
| 535 | * @param array $params Result of RequestContext::exportSession() |
||
| 536 | * @return ScopedCallback |
||
| 537 | * @throws MWException |
||
| 538 | * @since 1.21 |
||
| 539 | */ |
||
| 540 | public static function importScopedSession( array $params ) { |
||
| 623 | |||
| 624 | /** |
||
| 625 | * Create a new extraneous context. The context is filled with information |
||
| 626 | * external to the current session. |
||
| 627 | * - Title is specified by argument |
||
| 628 | * - Request is a FauxRequest, or a FauxRequest can be specified by argument |
||
| 629 | * - User is an anonymous user, for separation IPv4 localhost is used |
||
| 630 | * - Language will be based on the anonymous user and request, may be content |
||
| 631 | * language or a uselang param in the fauxrequest data may change the lang |
||
| 632 | * - Skin will be based on the anonymous user, should be the wiki's default skin |
||
| 633 | * |
||
| 634 | * @param Title $title Title to use for the extraneous request |
||
| 635 | * @param WebRequest|array $request A WebRequest or data to use for a FauxRequest |
||
| 636 | * @return RequestContext |
||
| 637 | */ |
||
| 638 | public static function newExtraneousContext( Title $title, $request = [] ) { |
||
| 650 | } |
||
| 651 |
Let’s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let’s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: