Complex classes like EntitySavingHelper 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 EntitySavingHelper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
35 | class EntitySavingHelper extends EntityLoadingHelper { |
||
36 | |||
37 | public const ASSIGN_FRESH_ID = 'assignFreshId'; |
||
38 | public const NO_FRESH_ID = 'noFreshId'; |
||
39 | |||
40 | /** |
||
41 | * @var SummaryFormatter |
||
42 | */ |
||
43 | private $summaryFormatter; |
||
44 | |||
45 | /** |
||
46 | * @var MediawikiEditEntityFactory |
||
47 | */ |
||
48 | private $editEntityFactory; |
||
49 | |||
50 | /** |
||
51 | * @var PermissionManager |
||
52 | */ |
||
53 | private $permissionManager; |
||
54 | |||
55 | /** |
||
56 | * Flags to pass to EditEntity::attemptSave; This is set by loadEntity() to EDIT_NEW |
||
57 | * for new entities, and EDIT_UPDATE for existing entities. |
||
58 | * |
||
59 | * @see EditEntity::attemptSave |
||
60 | * @see WikiPage::doEditContent |
||
61 | * |
||
62 | * @var int |
||
63 | */ |
||
64 | private $entitySavingFlags = 0; |
||
65 | |||
66 | /** |
||
67 | * Entity ID of the loaded entity. |
||
68 | * |
||
69 | * @var EntityId|null |
||
70 | */ |
||
71 | private $entityId = null; |
||
72 | |||
73 | /** |
||
74 | * Base revision ID, for loading the entity revision for editing, and for avoiding |
||
75 | * race conditions. |
||
76 | * |
||
77 | * @var int |
||
78 | */ |
||
79 | private $baseRevisionId = 0; |
||
80 | |||
81 | /** |
||
82 | * @var EntityFactory|null |
||
83 | */ |
||
84 | private $entityFactory = null; |
||
85 | |||
86 | /** |
||
87 | * @var EntityStore|null |
||
88 | */ |
||
89 | private $entityStore = null; |
||
90 | |||
91 | public function __construct( |
||
92 | ApiBase $apiModule, |
||
93 | EntityIdParser $idParser, |
||
94 | EntityRevisionLookup $entityRevisionLookup, |
||
95 | ApiErrorReporter $errorReporter, |
||
96 | SummaryFormatter $summaryFormatter, |
||
97 | MediawikiEditEntityFactory $editEntityFactory, |
||
98 | PermissionManager $permissionManager |
||
99 | ) { |
||
100 | parent::__construct( $apiModule, $idParser, $entityRevisionLookup, $errorReporter ); |
||
101 | |||
102 | $this->summaryFormatter = $summaryFormatter; |
||
103 | $this->editEntityFactory = $editEntityFactory; |
||
104 | $this->permissionManager = $permissionManager; |
||
105 | |||
106 | $this->defaultRetrievalMode = LookupConstants::LATEST_FROM_MASTER; |
||
107 | } |
||
108 | |||
109 | public function getBaseRevisionId(): int { |
||
110 | return $this->baseRevisionId; |
||
111 | } |
||
112 | |||
113 | public function getSaveFlags(): int { |
||
116 | |||
117 | public function getEntityFactory(): ?EntityFactory { |
||
118 | return $this->entityFactory; |
||
120 | |||
121 | public function setEntityFactory( EntityFactory $entityFactory ): void { |
||
124 | |||
125 | public function getEntityStore(): ?EntityStore { |
||
128 | |||
129 | public function setEntityStore( EntityStore $entityStore ): void { |
||
132 | |||
133 | /** |
||
134 | * @param EntityId|null $entityId ID of the entity to load. If not given, the ID is taken |
||
135 | * from the request parameters. If $entityId is given, the 'baserevid' parameter must |
||
136 | * belong to it. |
||
137 | * @param string $assignFreshId Whether to allow assigning entity ids to new entities. |
||
138 | * Either of the ASSIGN_FRESH_ID/NO_FRESH_ID constants. |
||
139 | * NOTE: We usually need to assign an ID early, for things like the ClaimIdGenerator. |
||
140 | * |
||
141 | * @throws ApiUsageException |
||
142 | * |
||
143 | * @return EntityDocument |
||
144 | */ |
||
145 | public function loadEntity( ?EntityId $entityId = null, $assignFreshId = self::ASSIGN_FRESH_ID ): EntityDocument { |
||
226 | |||
227 | private function isEntityCreationSupported(): bool { |
||
230 | |||
231 | /** |
||
232 | * Create an empty entity. |
||
233 | * |
||
234 | * @param string|null $entityType The type of entity to create. Optional if an ID is given. |
||
235 | * @param EntityId|null $customId Optionally assigns a specific ID instead of generating a new |
||
236 | * one. |
||
237 | * @param string $assignFreshId Either of the ASSIGN_FRESH_ID/NO_FRESH_ID constants |
||
238 | * NOTE: We usually need to assign an ID early, for things like the ClaimIdGenerator. |
||
239 | * |
||
240 | * @throws InvalidArgumentException when entity type and ID are given but do not match. |
||
241 | * @throws ApiUsageException |
||
242 | * @throws LogicException |
||
243 | * @return EntityDocument |
||
244 | */ |
||
245 | private function createEntity( $entityType, EntityId $customId = null, $assignFreshId = self::ASSIGN_FRESH_ID ): EntityDocument { |
||
295 | |||
296 | /** |
||
297 | * Attempts to save the new entity content, while first checking for permissions, |
||
298 | * edit conflicts, etc. Saving is done via EditEntityHandler::attemptSave(). |
||
299 | * |
||
300 | * This method automatically takes into account several parameters: |
||
301 | * * 'bot' for setting the bot flag |
||
302 | * * 'baserevid' for determining the edit's base revision for conflict resolution |
||
303 | * * 'token' for the edit token |
||
304 | * * 'tags' for change tags, assuming they were already permission checked by ApiBase |
||
305 | * (i.e. PARAM_TYPE => 'tags') |
||
306 | * |
||
307 | * If an error occurs, it is automatically reported and execution of the API module |
||
308 | * is terminated using the ApiErrorReporter (via handleStatus()). If there were any |
||
309 | * warnings, they will automatically be included in the API call's output (again, via |
||
310 | * handleStatus()). |
||
311 | * |
||
312 | * @param EntityDocument $entity The entity to save |
||
313 | * @param string|FormatableSummary $summary The edit summary |
||
314 | * @param int $flags The edit flags (see WikiPage::doEditContent) |
||
315 | * |
||
316 | * @throws LogicException if not in write mode |
||
317 | * @return Status the status of the save operation, as returned by EditEntityHandler::attemptSave() |
||
318 | * @see EditEntityHandler::attemptSave() |
||
319 | */ |
||
320 | public function attemptSaveEntity( EntityDocument $entity, $summary, int $flags = 0 ): Status { |
||
376 | |||
377 | /** |
||
378 | * @param array $params |
||
379 | * |
||
380 | * @return string|bool|null Token string, or false if not needed, or null if not set. |
||
381 | */ |
||
382 | private function evaluateTokenParam( array $params ) { |
||
391 | |||
392 | /** |
||
393 | * Signal errors and warnings from a save operation to the API call's output. |
||
394 | * This is much like handleStatus(), but specialized for Status objects returned by |
||
395 | * EditEntityHandler::attemptSave(). In particular, the 'errorFlags' and 'errorCode' fields |
||
396 | * from the status value are used to determine the error code to return to the caller. |
||
397 | * |
||
398 | * @note this function may or may not return normally, depending on whether |
||
399 | * the status is fatal or not. |
||
400 | * |
||
401 | * @see handleStatus(). |
||
402 | * |
||
403 | * @param Status $status The status to report |
||
404 | */ |
||
405 | private function handleSaveStatus( Status $status ): void { |
||
430 | |||
431 | /** |
||
432 | * Checks whether accessing array keys is safe, with e.g. @see DeprecatablePropertyArray |
||
433 | */ |
||
434 | private function isArrayLike( $value ): bool { |
||
437 | |||
438 | /** |
||
439 | * Include messages from a Status object in the API call's output. |
||
440 | * |
||
441 | * An ApiErrorHandler is used to report the status, if necessary. |
||
442 | * If $status->isOK() is false, this method will terminate with an ApiUsageException. |
||
443 | * |
||
444 | * @param Status $status The status to report |
||
445 | * @param string $errorCode The API error code to use in case $status->isOK() returns false |
||
446 | * |
||
447 | * @throws ApiUsageException If $status->isOK() returns false. |
||
448 | */ |
||
449 | private function handleStatus( Status $status, $errorCode ): void { |
||
459 | |||
460 | } |
||
461 |
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are 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.php
However, as
OtherDir/Foo.php
does 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: