Complex classes like MockRepository 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 MockRepository, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class MockRepository implements EntityLookup, EntityRedirectLookup, |
||
47 | EntityRevisionLookup, EntityStore, PropertyDataTypeLookup, SiteLinkLookup { |
||
48 | |||
49 | /** |
||
50 | * @var SiteLinkStore |
||
51 | */ |
||
52 | private $siteLinkStore; |
||
53 | |||
54 | /** |
||
55 | * Entity id serialization => array of EntityRevision |
||
56 | * |
||
57 | * @var array[] |
||
58 | */ |
||
59 | private $entities = []; |
||
60 | |||
61 | /** |
||
62 | * Log entries. Each entry has the following fields: |
||
63 | * revision, entity, summary, user, tags |
||
64 | * |
||
65 | * @var array[] |
||
66 | */ |
||
67 | private $log = []; |
||
68 | |||
69 | /** |
||
70 | * Entity id serialization => EntityRedirect |
||
71 | * |
||
72 | * @var RedirectRevision[] |
||
73 | */ |
||
74 | private $redirects = []; |
||
75 | |||
76 | /** |
||
77 | * User ID + Entity Id -> bool |
||
78 | * |
||
79 | * @var bool[] |
||
80 | */ |
||
81 | private $watchlist = []; |
||
82 | |||
83 | /** |
||
84 | * @var int |
||
85 | */ |
||
86 | private $maxEntityId = 0; |
||
87 | |||
88 | /** |
||
89 | * @var int |
||
90 | */ |
||
91 | private $maxRevisionId = 0; |
||
92 | |||
93 | public function __construct() { |
||
96 | |||
97 | /** |
||
98 | * @see EntityLookup::getEntity |
||
99 | * |
||
100 | * @param EntityId $entityId |
||
101 | * |
||
102 | * @return EntityDocument|null |
||
103 | * @throws StorageException |
||
104 | */ |
||
105 | public function getEntity( EntityId $entityId ) { |
||
110 | |||
111 | /** |
||
112 | * @see EntityRevisionLookup::getEntityRevision |
||
113 | * |
||
114 | * @param EntityId $entityId |
||
115 | * @param int $revisionId The desired revision id, or 0 for the latest revision. |
||
116 | * @param string $mode LATEST_FROM_REPLICA, LATEST_FROM_REPLICA_WITH_FALLBACK or |
||
117 | * LATEST_FROM_MASTER. |
||
118 | * |
||
119 | * @throws RevisionedUnresolvedRedirectException |
||
120 | * @throws StorageException |
||
121 | * @return EntityRevision|null |
||
122 | */ |
||
123 | public function getEntityRevision( |
||
168 | |||
169 | /** |
||
170 | * See EntityLookup::hasEntity() |
||
171 | * |
||
172 | * @param EntityId $entityId |
||
173 | * |
||
174 | * @return bool |
||
175 | */ |
||
176 | public function hasEntity( EntityId $entityId ) { |
||
179 | |||
180 | /** |
||
181 | * @see SiteLinkLookup::getItemIdForLink |
||
182 | * |
||
183 | * @param string $globalSiteId |
||
184 | * @param string $pageTitle |
||
185 | * |
||
186 | * @return ItemId|null |
||
187 | */ |
||
188 | public function getItemIdForLink( $globalSiteId, $pageTitle ) { |
||
191 | |||
192 | /** |
||
193 | * @see SiteLinkLookup::getItemIdForSiteLink |
||
194 | * |
||
195 | * @param SiteLink $siteLink |
||
196 | * |
||
197 | * @return ItemId|null |
||
198 | */ |
||
199 | public function getItemIdForSiteLink( SiteLink $siteLink ) { |
||
202 | |||
203 | /** |
||
204 | * Registers the sitelinks of the given Item so they can later be found with getLinks, etc |
||
205 | * |
||
206 | * @param Item $item |
||
207 | */ |
||
208 | private function registerSiteLinks( Item $item ) { |
||
211 | |||
212 | /** |
||
213 | * Unregisters the sitelinks of the given Item so they are no longer found with getLinks, etc |
||
214 | * |
||
215 | * @param ItemId $itemId |
||
216 | */ |
||
217 | private function unregisterSiteLinks( ItemId $itemId ) { |
||
220 | |||
221 | /** |
||
222 | * Puts an entity into the mock repository. If there already is an entity with the same ID |
||
223 | * in the mock repository, it is not removed, but replaced as the current one. If a revision |
||
224 | * ID is given, the entity with the highest revision ID is considered the current one. |
||
225 | * |
||
226 | * @param EntityDocument $entity |
||
227 | * @param int $revisionId |
||
228 | * @param int|string $timestamp |
||
229 | * @param User|string|null $user |
||
230 | * |
||
231 | * @throws StorageException |
||
232 | * @return EntityRevision |
||
233 | */ |
||
234 | public function putEntity( EntityDocument $entity, $revisionId = 0, $timestamp = 0, $user = null ) { |
||
284 | |||
285 | /** |
||
286 | * Puts a redirect into the mock repository. If there already is an entity with the same ID |
||
287 | * in the mock repository, it is replaced with the redirect. |
||
288 | * |
||
289 | * @param EntityRedirect $redirect |
||
290 | * @param int $revisionId |
||
291 | * @param string|int $timestamp |
||
292 | * |
||
293 | * @throws StorageException |
||
294 | */ |
||
295 | public function putRedirect( EntityRedirect $redirect, $revisionId = 0, $timestamp = 0 ) { |
||
315 | |||
316 | /** |
||
317 | * Removes an entity from the mock repository. |
||
318 | * |
||
319 | * @param EntityId $entityId |
||
320 | * |
||
321 | * @return EntityDocument |
||
322 | */ |
||
323 | public function removeEntity( EntityId $entityId ) { |
||
341 | |||
342 | /** |
||
343 | * @see SiteLinkLookup::getLinks |
||
344 | * |
||
345 | * @param int[] $numericIds Numeric (unprefixed) item ids |
||
346 | * @param string[] $siteIds |
||
347 | * @param string[] $pageNames |
||
348 | * |
||
349 | * @return array[] |
||
350 | */ |
||
351 | public function getLinks( array $numericIds = [], array $siteIds = [], array $pageNames = [] ) { |
||
354 | |||
355 | /** |
||
356 | * Fetches the entities with provided ids and returns them. |
||
357 | * The result array contains the prefixed entity ids as keys. |
||
358 | * The values are either an EntityDocument or null, if there is no entity with the associated id. |
||
359 | * |
||
360 | * The revisions can be specified as an array holding an integer element for each |
||
361 | * id in the $entityIds array or false for latest. If all should be latest, false |
||
362 | * can be provided instead of an array. |
||
363 | * |
||
364 | * @param EntityId[] $entityIds |
||
365 | * |
||
366 | * @return EntityDocument[]|null[] |
||
367 | */ |
||
368 | public function getEntities( array $entityIds ) { |
||
381 | |||
382 | /** |
||
383 | * @see SiteLinkLookup::getSiteLinksForItem |
||
384 | * |
||
385 | * @param ItemId $itemId |
||
386 | * |
||
387 | * @return SiteLink[] |
||
388 | */ |
||
389 | public function getSiteLinksForItem( ItemId $itemId ) { |
||
392 | |||
393 | /** |
||
394 | * @param string $propertyLabel |
||
395 | * @param string $languageCode |
||
396 | * |
||
397 | * @return EntityDocument|null |
||
398 | */ |
||
399 | public function getPropertyByLabel( $propertyLabel, $languageCode ) { |
||
424 | |||
425 | /** |
||
426 | * @see PropertyDataTypeLookup::getDataTypeIdForProperty |
||
427 | * |
||
428 | * @param PropertyId $propertyId |
||
429 | * |
||
430 | * @return string |
||
431 | * @throws PropertyDataTypeLookupException |
||
432 | */ |
||
433 | public function getDataTypeIdForProperty( PropertyId $propertyId ) { |
||
442 | |||
443 | /** |
||
444 | * @see EntityRevisionLookup::getLatestRevisionId |
||
445 | * |
||
446 | * @param EntityId $entityId |
||
447 | * @param string $mode |
||
448 | * |
||
449 | * @return LatestRevisionIdResult |
||
450 | */ |
||
451 | public function getLatestRevisionId( EntityId $entityId, $mode = LookupConstants::LATEST_FROM_REPLICA ) { |
||
462 | |||
463 | /** |
||
464 | * Stores the given Entity. |
||
465 | * |
||
466 | * @param EntityDocument $entity the entity to save. |
||
467 | * @param string $summary ignored |
||
468 | * @param User $user ignored |
||
469 | * @param int $flags EDIT_XXX flags, as defined for WikiPage::doEditContent. |
||
470 | * @param int|bool $baseRevisionId the revision ID $entity is based on. Saving should fail if |
||
471 | * $baseRevId is no longer the current revision. |
||
472 | * @param string[] $tags added to log entry |
||
473 | * |
||
474 | * @see WikiPage::doEditContent |
||
475 | * |
||
476 | * @return EntityRevision |
||
477 | * @throws StorageException |
||
478 | */ |
||
479 | public function saveEntity( EntityDocument $entity, $summary, User $user, $flags = 0, $baseRevisionId = false, array $tags = [] ) { |
||
510 | |||
511 | /** |
||
512 | * @see EntityStore::saveRedirect |
||
513 | * |
||
514 | * @param EntityRedirect $redirect |
||
515 | * @param string $summary |
||
516 | * @param User $user |
||
517 | * @param int $flags |
||
518 | * @param int|bool $baseRevisionId |
||
519 | * |
||
520 | * @throws StorageException If the given type of entity does not support redirects |
||
521 | * @return int The revision id created by storing the redirect |
||
522 | */ |
||
523 | public function saveRedirect( EntityRedirect $redirect, $summary, User $user, $flags = 0, $baseRevisionId = false ) { |
||
535 | |||
536 | /** |
||
537 | * Deletes the given entity in some underlying storage mechanism. |
||
538 | * |
||
539 | * @param EntityId $entityId |
||
540 | * @param string $reason the reason for deletion |
||
541 | * @param User $user |
||
542 | */ |
||
543 | public function deleteEntity( EntityId $entityId, $reason, User $user ) { |
||
546 | |||
547 | /** |
||
548 | * Check if no edits were made by other users since the given revision. |
||
549 | * This makes the assumption that revision ids are monotonically increasing. |
||
550 | * |
||
551 | * @see EditPage::userWasLastToEdit |
||
552 | * |
||
553 | * @param User $user |
||
554 | * @param EntityId $entityId the entity to check |
||
555 | * @param int $lastRevisionId the revision to check from |
||
556 | * |
||
557 | * @return bool |
||
558 | */ |
||
559 | public function userWasLastToEdit( User $user, EntityId $entityId, $lastRevisionId ) { |
||
576 | |||
577 | /** |
||
578 | * Watches or unwatches the entity. |
||
579 | * |
||
580 | * @param User $user |
||
581 | * @param EntityId $entityId the entity to watch |
||
582 | * @param bool $watch whether to watch or unwatch the page. |
||
583 | */ |
||
584 | public function updateWatchlist( User $user, EntityId $entityId, $watch ) { |
||
591 | |||
592 | /** |
||
593 | * Determines whether the given user is watching the given item |
||
594 | * |
||
595 | * @param User $user |
||
596 | * @param EntityId $entityId the entity to watch |
||
597 | * |
||
598 | * @return bool |
||
599 | */ |
||
600 | public function isWatching( User $user, EntityId $entityId ) { |
||
603 | |||
604 | /** |
||
605 | * @param EntityId $id |
||
606 | * |
||
607 | * @throws StorageException |
||
608 | */ |
||
609 | private function updateMaxNumericId( EntityId $id ) { |
||
616 | |||
617 | /** |
||
618 | * @see EntityStore::assignFreshId |
||
619 | * |
||
620 | * @param EntityDocument $entity |
||
621 | * |
||
622 | * @throws InvalidArgumentException when the entity type does not support setting numeric ids. |
||
623 | */ |
||
624 | public function assignFreshId( EntityDocument $entity ) { |
||
641 | |||
642 | /** |
||
643 | * @param string $idString |
||
644 | * |
||
645 | * @return ItemId|PropertyId |
||
646 | */ |
||
647 | private function parseId( $idString ) { |
||
651 | |||
652 | /** |
||
653 | * @param int $revisionId |
||
654 | * @param EntityId|string $entityId |
||
655 | * @param string $summary |
||
656 | * @param User|string $user |
||
657 | * @param string[] $tags |
||
658 | */ |
||
659 | private function putLog( $revisionId, $entityId, $summary, $user, array $tags = [] ) { |
||
676 | |||
677 | /** |
||
678 | * Returns the log entry for the given revision Id. |
||
679 | * |
||
680 | * @param int $revisionId |
||
681 | * |
||
682 | * @return array|null An associative array containing the fields |
||
683 | * 'revision', 'entity', 'summary', 'user', and 'tags'. |
||
684 | */ |
||
685 | public function getLogEntry( $revisionId ) { |
||
688 | |||
689 | /** |
||
690 | * Returns the newest (according to the revision id) log entry |
||
691 | * for the given entity. |
||
692 | * |
||
693 | * @param EntityId|string $entityId |
||
694 | * |
||
695 | * @return array|null An associative array containing the fields |
||
696 | * 'revision', 'entity', 'summary', 'user', and 'tags'. |
||
697 | */ |
||
698 | public function getLatestLogEntryFor( $entityId ) { |
||
715 | |||
716 | /** |
||
717 | * Returns the IDs that redirect to (are aliases of) the given target entity. |
||
718 | * |
||
719 | * @param EntityId $targetId |
||
720 | * |
||
721 | * @return EntityId[] |
||
722 | */ |
||
723 | public function getRedirectIds( EntityId $targetId ) { |
||
735 | |||
736 | /** |
||
737 | * Returns the redirect target associated with the given redirect ID. |
||
738 | * |
||
739 | * @param EntityId $entityId |
||
740 | * @param string $forUpdate |
||
741 | * |
||
742 | * @return EntityId|null The ID of the redirect target, or null if $entityId |
||
743 | * does not refer to a redirect |
||
744 | * @throws EntityRedirectLookupException |
||
745 | */ |
||
746 | public function getRedirectForEntityId( EntityId $entityId, $forUpdate = '' ) { |
||
759 | |||
760 | /** |
||
761 | * @see EntityStore::canCreateWithCustomId |
||
762 | * |
||
763 | * @param EntityId $id |
||
764 | * |
||
765 | * @return bool |
||
766 | */ |
||
767 | public function canCreateWithCustomId( EntityId $id ) { |
||
770 | |||
771 | } |
||
772 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: