| Total Complexity | 52 | 
| Total Lines | 490 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like Edit 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.
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 Edit, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 15 | class Edit extends Model  | 
            ||
| 16 | { | 
            ||
| 17 | public const DELETED_TEXT = 1;  | 
            ||
| 18 | public const DELETED_COMMENT = 2;  | 
            ||
| 19 | public const DELETED_USER = 4;  | 
            ||
| 20 | public const DELETED_RESTRICTED = 8;  | 
            ||
| 21 | |||
| 22 | protected UserRepository $userRepo;  | 
            ||
| 23 | |||
| 24 | /** @var int ID of the revision */  | 
            ||
| 25 | protected int $id;  | 
            ||
| 26 | |||
| 27 | /** @var DateTime Timestamp of the revision */  | 
            ||
| 28 | protected DateTime $timestamp;  | 
            ||
| 29 | |||
| 30 | /** @var bool Whether or not this edit was a minor edit */  | 
            ||
| 31 | protected bool $minor;  | 
            ||
| 32 | |||
| 33 | /** @var int|null Length of the page as of this edit, in bytes */  | 
            ||
| 34 | protected ?int $length;  | 
            ||
| 35 | |||
| 36 | /** @var int|null The diff size of this edit */  | 
            ||
| 37 | protected ?int $lengthChange;  | 
            ||
| 38 | |||
| 39 | /** @var string The edit summary */  | 
            ||
| 40 | protected string $comment;  | 
            ||
| 41 | |||
| 42 | /** @var string|null The SHA-1 of the wikitext as of the revision. */  | 
            ||
| 43 | protected ?string $sha = null;  | 
            ||
| 44 | |||
| 45 | /** @var bool|null Whether this edit was later reverted. */  | 
            ||
| 46 | protected ?bool $reverted;  | 
            ||
| 47 | |||
| 48 | /** @var int Deletion status of the revision. */  | 
            ||
| 49 | protected int $deleted;  | 
            ||
| 50 | |||
| 51 | /**  | 
            ||
| 52 | * Edit constructor.  | 
            ||
| 53 | * @param EditRepository $repository  | 
            ||
| 54 | * @param UserRepository $userRepo  | 
            ||
| 55 | * @param Page $page  | 
            ||
| 56 | * @param string[] $attrs Attributes, as retrieved by PageRepository::getRevisions()  | 
            ||
| 57 | */  | 
            ||
| 58 | public function __construct(EditRepository $repository, UserRepository $userRepo, Page $page, array $attrs = [])  | 
            ||
| 59 |     { | 
            ||
| 60 | $this->repository = $repository;  | 
            ||
| 61 | $this->userRepo = $userRepo;  | 
            ||
| 62 | $this->page = $page;  | 
            ||
| 63 | |||
| 64 | // Copy over supported attributes  | 
            ||
| 65 | $this->id = isset($attrs['id']) ? (int)$attrs['id'] : (int)$attrs['rev_id'];  | 
            ||
| 66 | |||
| 67 | // Allow DateTime or string (latter assumed to be of format YmdHis)  | 
            ||
| 68 |         if ($attrs['timestamp'] instanceof DateTime) { | 
            ||
| 69 | $this->timestamp = $attrs['timestamp'];  | 
            ||
| 70 |         } else { | 
            ||
| 71 |             $this->timestamp = DateTime::createFromFormat('YmdHis', $attrs['timestamp']); | 
            ||
| 72 | }  | 
            ||
| 73 | |||
| 74 | $this->deleted = (int)($attrs['rev_deleted'] ?? 0);  | 
            ||
| 75 | |||
| 76 |         if (($this->deleted & self::DELETED_USER) || ($this->deleted & self::DELETED_RESTRICTED)) { | 
            ||
| 77 | $this->user = null;  | 
            ||
| 78 |         } else { | 
            ||
| 79 | $this->user = $attrs['user'] ?? ($attrs['username'] ? new User($this->userRepo, $attrs['username']) : null);  | 
            ||
| 80 | }  | 
            ||
| 81 | |||
| 82 | $this->minor = 1 === (int)$attrs['minor'];  | 
            ||
| 83 | $this->length = isset($attrs['length']) ? (int)$attrs['length'] : null;  | 
            ||
| 84 | $this->lengthChange = isset($attrs['length_change']) ? (int)$attrs['length_change'] : null;  | 
            ||
| 85 | $this->comment = $attrs['comment'] ?? '';  | 
            ||
| 86 | |||
| 87 |         if (isset($attrs['rev_sha1']) || isset($attrs['sha'])) { | 
            ||
| 88 | $this->sha = $attrs['rev_sha1'] ?? $attrs['sha'];  | 
            ||
| 89 | }  | 
            ||
| 90 | |||
| 91 | // This can be passed in to save as a property on the Edit instance.  | 
            ||
| 92 | // Note that the Edit class knows nothing about it's value, and  | 
            ||
| 93 | // is not capable of detecting whether the given edit was actually reverted.  | 
            ||
| 94 | $this->reverted = isset($attrs['reverted']) ? (bool)$attrs['reverted'] : null;  | 
            ||
| 95 | }  | 
            ||
| 96 | |||
| 97 | /**  | 
            ||
| 98 | * Get Edits given revision rows (JOINed on the page table).  | 
            ||
| 99 | * @param PageRepository $pageRepo  | 
            ||
| 100 | * @param EditRepository $editRepo  | 
            ||
| 101 | * @param UserRepository $userRepo  | 
            ||
| 102 | * @param Project $project  | 
            ||
| 103 | * @param User $user  | 
            ||
| 104 | * @param array $revs Each must contain 'page_title' and 'namespace'.  | 
            ||
| 105 | * @return Edit[]  | 
            ||
| 106 | */  | 
            ||
| 107 | public static function getEditsFromRevs(  | 
            ||
| 108 | PageRepository $pageRepo,  | 
            ||
| 109 | EditRepository $editRepo,  | 
            ||
| 110 | UserRepository $userRepo,  | 
            ||
| 111 | Project $project,  | 
            ||
| 112 | User $user,  | 
            ||
| 113 | array $revs  | 
            ||
| 114 |     ): array { | 
            ||
| 115 |         return array_map(function ($rev) use ($pageRepo, $editRepo, $userRepo, $project, $user) { | 
            ||
| 116 | /** Page object to be passed to the Edit constructor. */  | 
            ||
| 117 | $page = Page::newFromRow($pageRepo, $project, $rev);  | 
            ||
| 118 | $rev['user'] = $user;  | 
            ||
| 119 | |||
| 120 | return new self($editRepo, $userRepo, $page, $rev);  | 
            ||
| 121 | }, $revs);  | 
            ||
| 122 | }  | 
            ||
| 123 | |||
| 124 | /**  | 
            ||
| 125 | * Unique identifier for this Edit, to be used in cache keys.  | 
            ||
| 126 | * @see Repository::getCacheKey()  | 
            ||
| 127 | * @return string  | 
            ||
| 128 | */  | 
            ||
| 129 | public function getCacheKey(): string  | 
            ||
| 130 |     { | 
            ||
| 131 | return (string)$this->id;  | 
            ||
| 132 | }  | 
            ||
| 133 | |||
| 134 | /**  | 
            ||
| 135 | * ID of the edit.  | 
            ||
| 136 | * @return int  | 
            ||
| 137 | */  | 
            ||
| 138 | public function getId(): int  | 
            ||
| 139 |     { | 
            ||
| 140 | return $this->id;  | 
            ||
| 141 | }  | 
            ||
| 142 | |||
| 143 | /**  | 
            ||
| 144 | * Get the edit's timestamp.  | 
            ||
| 145 | * @return DateTime  | 
            ||
| 146 | */  | 
            ||
| 147 | public function getTimestamp(): DateTime  | 
            ||
| 150 | }  | 
            ||
| 151 | |||
| 152 | /**  | 
            ||
| 153 | * Get the edit's timestamp as a UTC string, as with YYYY-MM-DDTHH:MM:SSZ  | 
            ||
| 154 | * @return string  | 
            ||
| 155 | */  | 
            ||
| 156 | public function getUTCTimestamp(): string  | 
            ||
| 157 |     { | 
            ||
| 158 |         return $this->getTimestamp()->format('Y-m-d\TH:i:s\Z'); | 
            ||
| 159 | }  | 
            ||
| 160 | |||
| 161 | /**  | 
            ||
| 162 | * Year the revision was made.  | 
            ||
| 163 | * @return string  | 
            ||
| 164 | */  | 
            ||
| 165 | public function getYear(): string  | 
            ||
| 166 |     { | 
            ||
| 167 |         return $this->timestamp->format('Y'); | 
            ||
| 168 | }  | 
            ||
| 169 | |||
| 170 | /**  | 
            ||
| 171 | * Get the numeric representation of the month the revision was made, with leading zeros.  | 
            ||
| 172 | * @return string  | 
            ||
| 173 | */  | 
            ||
| 174 | public function getMonth(): string  | 
            ||
| 175 |     { | 
            ||
| 176 |         return $this->timestamp->format('m'); | 
            ||
| 177 | }  | 
            ||
| 178 | |||
| 179 | /**  | 
            ||
| 180 | * Whether or not this edit was a minor edit.  | 
            ||
| 181 | * @return bool  | 
            ||
| 182 | */  | 
            ||
| 183 | public function getMinor(): bool  | 
            ||
| 186 | }  | 
            ||
| 187 | |||
| 188 | /**  | 
            ||
| 189 | * Alias of getMinor()  | 
            ||
| 190 | * @return bool Whether or not this edit was a minor edit  | 
            ||
| 191 | */  | 
            ||
| 192 | public function isMinor(): bool  | 
            ||
| 193 |     { | 
            ||
| 194 | return $this->getMinor();  | 
            ||
| 195 | }  | 
            ||
| 196 | |||
| 197 | /**  | 
            ||
| 198 | * Length of the page as of this edit, in bytes.  | 
            ||
| 199 | * @see Edit::getSize() Edit::getSize() for the size <em>change</em>.  | 
            ||
| 200 | * @return int|null  | 
            ||
| 201 | */  | 
            ||
| 202 | public function getLength(): ?int  | 
            ||
| 203 |     { | 
            ||
| 204 | return $this->length;  | 
            ||
| 205 | }  | 
            ||
| 206 | |||
| 207 | /**  | 
            ||
| 208 | * The diff size of this edit.  | 
            ||
| 209 | * @return int|null Signed length change in bytes.  | 
            ||
| 210 | */  | 
            ||
| 211 | public function getSize(): ?int  | 
            ||
| 212 |     { | 
            ||
| 213 | return $this->lengthChange;  | 
            ||
| 214 | }  | 
            ||
| 215 | |||
| 216 | /**  | 
            ||
| 217 | * Alias of getSize()  | 
            ||
| 218 | * @return int|null The diff size of this edit  | 
            ||
| 219 | */  | 
            ||
| 220 | public function getLengthChange(): ?int  | 
            ||
| 221 |     { | 
            ||
| 222 | return $this->getSize();  | 
            ||
| 223 | }  | 
            ||
| 224 | |||
| 225 | /**  | 
            ||
| 226 | * Get the user who made the edit.  | 
            ||
| 227 | * @return User|null null can happen for instance if the username was suppressed.  | 
            ||
| 228 | */  | 
            ||
| 229 | public function getUser(): ?User  | 
            ||
| 232 | }  | 
            ||
| 233 | |||
| 234 | /**  | 
            ||
| 235 | * Get the edit summary.  | 
            ||
| 236 | * @return string  | 
            ||
| 237 | */  | 
            ||
| 238 | public function getComment(): string  | 
            ||
| 239 |     { | 
            ||
| 240 | return (string)$this->comment;  | 
            ||
| 241 | }  | 
            ||
| 242 | |||
| 243 | /**  | 
            ||
| 244 | * Get the edit summary (alias of Edit::getComment()).  | 
            ||
| 245 | * @return string  | 
            ||
| 246 | */  | 
            ||
| 247 | public function getSummary(): string  | 
            ||
| 248 |     { | 
            ||
| 249 | return $this->getComment();  | 
            ||
| 250 | }  | 
            ||
| 251 | |||
| 252 | /**  | 
            ||
| 253 | * Get the SHA-1 of the revision.  | 
            ||
| 254 | * @return string|null  | 
            ||
| 255 | */  | 
            ||
| 256 | public function getSha(): ?string  | 
            ||
| 257 |     { | 
            ||
| 258 | return $this->sha;  | 
            ||
| 259 | }  | 
            ||
| 260 | |||
| 261 | /**  | 
            ||
| 262 | * Was this edit reported as having been reverted?  | 
            ||
| 263 | * The value for this is merely passed in from precomputed data.  | 
            ||
| 264 | * @return bool|null  | 
            ||
| 265 | */  | 
            ||
| 266 | public function isReverted(): ?bool  | 
            ||
| 267 |     { | 
            ||
| 268 | return $this->reverted;  | 
            ||
| 269 | }  | 
            ||
| 270 | |||
| 271 | /**  | 
            ||
| 272 | * Set the reverted property.  | 
            ||
| 273 | * @param bool $reverted  | 
            ||
| 274 | */  | 
            ||
| 275 | public function setReverted(bool $reverted): void  | 
            ||
| 278 | }  | 
            ||
| 279 | |||
| 280 | /**  | 
            ||
| 281 | * Get deletion status of the revision.  | 
            ||
| 282 | * @return int  | 
            ||
| 283 | */  | 
            ||
| 284 | public function getDeleted(): int  | 
            ||
| 285 |     { | 
            ||
| 286 | return $this->deleted;  | 
            ||
| 287 | }  | 
            ||
| 288 | |||
| 289 | /**  | 
            ||
| 290 | * Was the username deleted from public view?  | 
            ||
| 291 | * @return bool  | 
            ||
| 292 | */  | 
            ||
| 293 | public function deletedUser(): bool  | 
            ||
| 296 | }  | 
            ||
| 297 | |||
| 298 | /**  | 
            ||
| 299 | * Was the edit summary deleted from public view?  | 
            ||
| 300 | * @return bool  | 
            ||
| 301 | */  | 
            ||
| 302 | public function deletedSummary(): bool  | 
            ||
| 303 |     { | 
            ||
| 304 | return ($this->deleted & self::DELETED_COMMENT) > 0;  | 
            ||
| 305 | }  | 
            ||
| 306 | |||
| 307 | /**  | 
            ||
| 308 | * Get edit summary as 'wikified' HTML markup  | 
            ||
| 309 | * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid  | 
            ||
| 310 | * an API call. This should be used only if you fetched the page title via other  | 
            ||
| 311 | * means (SQL query), and is not from user input alone.  | 
            ||
| 312 | * @return string Safe HTML  | 
            ||
| 313 | */  | 
            ||
| 314 | public function getWikifiedComment(bool $useUnnormalizedPageTitle = false): string  | 
            ||
| 321 | );  | 
            ||
| 322 | }  | 
            ||
| 323 | |||
| 324 | /**  | 
            ||
| 325 | * Public static method to wikify a summary, can be used on any arbitrary string.  | 
            ||
| 326 | * Does NOT support section links unless you specify a page.  | 
            ||
| 327 | * @param string $summary  | 
            ||
| 328 | * @param Project $project  | 
            ||
| 329 | * @param Page|null $page  | 
            ||
| 330 | * @param bool $useUnnormalizedPageTitle Use the unnormalized page title to avoid  | 
            ||
| 331 | * an API call. This should be used only if you fetched the page title via other  | 
            ||
| 332 | * means (SQL query), and is not from user input alone.  | 
            ||
| 333 | * @static  | 
            ||
| 334 | * @return string  | 
            ||
| 335 | */  | 
            ||
| 336 | public static function wikifyString(  | 
            ||
| 337 | string $summary,  | 
            ||
| 338 | Project $project,  | 
            ||
| 339 | ?Page $page = null,  | 
            ||
| 340 | bool $useUnnormalizedPageTitle = false  | 
            ||
| 341 |     ): string { | 
            ||
| 342 | $summary = htmlspecialchars(html_entity_decode($summary), ENT_NOQUOTES);  | 
            ||
| 343 | |||
| 344 | // First link raw URLs. Courtesy of https://stackoverflow.com/a/11641499/604142  | 
            ||
| 345 | $summary = preg_replace(  | 
            ||
| 346 | '%\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))%s',  | 
            ||
| 347 | '<a target="_blank" href="$1">$1</a>',  | 
            ||
| 348 | $summary  | 
            ||
| 349 | );  | 
            ||
| 350 | |||
| 351 | $sectionMatch = null;  | 
            ||
| 352 |         $isSection = preg_match_all("/^\/\* (.*?) \*\//", $summary, $sectionMatch); | 
            ||
| 353 | |||
| 354 |         if ($isSection && isset($page)) { | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 355 | $pageUrl = $project->getUrlForPage($page->getTitle($useUnnormalizedPageTitle));  | 
            ||
| 356 | $sectionTitle = $sectionMatch[1][0];  | 
            ||
| 357 | |||
| 358 | // Must have underscores for the link to properly go to the section.  | 
            ||
| 359 |             $sectionTitleLink = htmlspecialchars(str_replace(' ', '_', $sectionTitle)); | 
            ||
| 360 | |||
| 361 | $sectionWikitext = "<a target='_blank' href='$pageUrl#$sectionTitleLink'>→</a>" .  | 
            ||
| 362 | "<em class='text-muted'>" . htmlspecialchars($sectionTitle) . ":</em> ";  | 
            ||
| 363 | $summary = str_replace($sectionMatch[0][0], $sectionWikitext, $summary);  | 
            ||
| 364 | }  | 
            ||
| 365 | |||
| 366 | $linkMatch = null;  | 
            ||
| 367 | |||
| 368 |         while (preg_match_all("/\[\[:?(.*?)]]/", $summary, $linkMatch)) { | 
            ||
| 369 |             $wikiLinkParts = explode('|', $linkMatch[1][0]); | 
            ||
| 370 | $wikiLinkPath = htmlspecialchars($wikiLinkParts[0]);  | 
            ||
| 371 | $wikiLinkText = htmlspecialchars(  | 
            ||
| 372 | $wikiLinkParts[1] ?? $wikiLinkPath  | 
            ||
| 373 | );  | 
            ||
| 374 | |||
| 375 | // Use normalized page title (underscored, capitalized).  | 
            ||
| 376 |             $pageUrl = $project->getUrlForPage(ucfirst(str_replace(' ', '_', $wikiLinkPath))); | 
            ||
| 377 | |||
| 378 | $link = "<a target='_blank' href='$pageUrl'>$wikiLinkText</a>";  | 
            ||
| 379 | $summary = str_replace($linkMatch[0][0], $link, $summary);  | 
            ||
| 380 | }  | 
            ||
| 381 | |||
| 382 | return $summary;  | 
            ||
| 383 | }  | 
            ||
| 384 | |||
| 385 | /**  | 
            ||
| 386 | * Get edit summary as 'wikified' HTML markup (alias of Edit::getWikifiedComment()).  | 
            ||
| 387 | * @return string  | 
            ||
| 388 | */  | 
            ||
| 389 | public function getWikifiedSummary(): string  | 
            ||
| 390 |     { | 
            ||
| 391 | return $this->getWikifiedComment();  | 
            ||
| 392 | }  | 
            ||
| 393 | |||
| 394 | /**  | 
            ||
| 395 | * Get the project this edit was made on  | 
            ||
| 396 | * @return Project  | 
            ||
| 397 | */  | 
            ||
| 398 | public function getProject(): Project  | 
            ||
| 399 |     { | 
            ||
| 400 | return $this->getPage()->getProject();  | 
            ||
| 401 | }  | 
            ||
| 402 | |||
| 403 | /**  | 
            ||
| 404 | * Get the full URL to the diff of the edit  | 
            ||
| 405 | * @return string  | 
            ||
| 406 | */  | 
            ||
| 407 | public function getDiffUrl(): string  | 
            ||
| 408 |     { | 
            ||
| 409 |         return rtrim($this->getProject()->getUrlForPage('Special:Diff/' . $this->id), '/'); | 
            ||
| 410 | }  | 
            ||
| 411 | |||
| 412 | /**  | 
            ||
| 413 | * Get the full permanent URL to the page at the time of the edit  | 
            ||
| 414 | * @return string  | 
            ||
| 415 | */  | 
            ||
| 416 | public function getPermaUrl(): string  | 
            ||
| 417 |     { | 
            ||
| 418 |         return rtrim($this->getProject()->getUrlForPage('Special:PermaLink/' . $this->id), '/'); | 
            ||
| 419 | }  | 
            ||
| 420 | |||
| 421 | /**  | 
            ||
| 422 | * Was the edit a revert, based on the edit summary?  | 
            ||
| 423 | * @return bool  | 
            ||
| 424 | */  | 
            ||
| 425 | public function isRevert(): bool  | 
            ||
| 426 |     { | 
            ||
| 427 | return $this->repository->getAutoEditsHelper()->isRevert($this->comment, $this->getProject());  | 
            ||
| 428 | }  | 
            ||
| 429 | |||
| 430 | /**  | 
            ||
| 431 | * Get the name of the tool that was used to make this edit.  | 
            ||
| 432 | * @return array|null The name of the tool(s) that was used to make the edit.  | 
            ||
| 433 | */  | 
            ||
| 434 | public function getTool(): ?array  | 
            ||
| 435 |     { | 
            ||
| 436 | return $this->repository->getAutoEditsHelper()->getTool($this->comment, $this->getProject());  | 
            ||
| 437 | }  | 
            ||
| 438 | |||
| 439 | /**  | 
            ||
| 440 | * Was the edit (semi-)automated, based on the edit summary?  | 
            ||
| 441 | * @return bool  | 
            ||
| 442 | */  | 
            ||
| 443 | public function isAutomated(): bool  | 
            ||
| 444 |     { | 
            ||
| 445 | return (bool)$this->getTool();  | 
            ||
| 446 | }  | 
            ||
| 447 | |||
| 448 | /**  | 
            ||
| 449 | * Was the edit made by a logged out user?  | 
            ||
| 450 | * @return bool|null  | 
            ||
| 451 | */  | 
            ||
| 452 | public function isAnon(): ?bool  | 
            ||
| 453 |     { | 
            ||
| 454 | return $this->getUser() ? $this->getUser()->isAnon() : null;  | 
            ||
| 455 | }  | 
            ||
| 456 | |||
| 457 | /**  | 
            ||
| 458 | * Get HTML for the diff of this Edit.  | 
            ||
| 459 | * @return string|null Raw HTML, must be wrapped in a <table> tag. Null if no comparison could be made.  | 
            ||
| 460 | */  | 
            ||
| 461 | public function getDiffHtml(): ?string  | 
            ||
| 464 | }  | 
            ||
| 465 | |||
| 466 | /**  | 
            ||
| 467 | * Formats the data as an array for use in JSON APIs.  | 
            ||
| 468 | * @param bool $includeProject  | 
            ||
| 469 | * @return array  | 
            ||
| 470 | * @internal This method assumes the Edit was constructed with data already filled in from a database query.  | 
            ||
| 471 | */  | 
            ||
| 472 | public function getForJson(bool $includeProject = false): array  | 
            ||
| 473 |     { | 
            ||
| 507 | 
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
integervalues, zero is a special case, in particular the following results might be unexpected: