Complex classes like Fields 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 Fields, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 34 | class Fields extends SortableFields |
||
| 35 | { |
||
| 36 | /** |
||
| 37 | * @inheritdoc |
||
| 38 | */ |
||
| 39 | const SOURCE_ATTRIBUTE = MetaRecord::SOURCE_ATTRIBUTE; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @inheritdoc |
||
| 43 | */ |
||
| 44 | const TARGET_ATTRIBUTE = MetaRecord::TARGET_ATTRIBUTE; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * @inheritdoc |
||
| 48 | */ |
||
| 49 | protected static function tableAlias(): string |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @param FieldInterface $field |
||
| 56 | * @throws Exception |
||
| 57 | */ |
||
| 58 | private function ensureField(FieldInterface $field) |
||
| 68 | |||
| 69 | /** |
||
| 70 | * @inheritdoc |
||
| 71 | */ |
||
| 72 | public function getQuery( |
||
| 86 | |||
| 87 | |||
| 88 | /******************************************* |
||
| 89 | * NORMALIZE VALUE |
||
| 90 | *******************************************/ |
||
| 91 | |||
| 92 | /** |
||
| 93 | * @param FieldInterface $field |
||
| 94 | * @param $value |
||
| 95 | * @param ElementInterface|null $element |
||
| 96 | * @return array |
||
| 97 | */ |
||
| 98 | public function serializeValue( |
||
| 117 | |||
| 118 | /******************************************* |
||
| 119 | * NORMALIZE VALUE |
||
| 120 | *******************************************/ |
||
| 121 | |||
| 122 | /** |
||
| 123 | * Accepts input data and converts it into an array of associated Meta elements |
||
| 124 | * |
||
| 125 | * @param FieldInterface $field |
||
| 126 | * @param SortableAssociationQueryInterface $query |
||
| 127 | * @param array $value |
||
| 128 | * @param ElementInterface|null $element |
||
| 129 | */ |
||
| 130 | protected function normalizeQueryInputValues( |
||
| 193 | |||
| 194 | /** |
||
| 195 | * @param MetaField $field |
||
| 196 | * @param array $values |
||
| 197 | * @param ElementInterface $element |
||
| 198 | * @return array |
||
| 199 | */ |
||
| 200 | protected function getExistingValues(MetaField $field, array $values, ElementInterface $element): array |
||
| 228 | |||
| 229 | |||
| 230 | /******************************************* |
||
| 231 | * ELEMENT EVENTS |
||
| 232 | *******************************************/ |
||
| 233 | |||
| 234 | /** |
||
| 235 | * @param MetaField $field |
||
| 236 | * @param ElementInterface $element |
||
| 237 | * @return bool |
||
| 238 | * @throws \Throwable |
||
| 239 | */ |
||
| 240 | public function beforeElementDelete(MetaField $field, ElementInterface $element): bool |
||
| 259 | |||
| 260 | /** |
||
| 261 | * @param Meta $field |
||
| 262 | * @param ElementInterface $owner |
||
| 263 | * @throws \Exception |
||
| 264 | * @throws \Throwable |
||
| 265 | * @throws \yii\db\Exception |
||
| 266 | */ |
||
| 267 | public function afterElementSave(MetaField $field, ElementInterface $owner) |
||
| 268 | { |
||
| 269 | /** @var Element $owner */ |
||
| 270 | |||
| 271 | /** @var MetaQuery $query */ |
||
| 272 | $query = $owner->getFieldValue($field->handle); |
||
| 273 | |||
| 274 | // Skip if the query's site ID is different than the element's |
||
| 275 | // (Indicates that the value as copied from another site for element propagation) |
||
| 276 | if ($query->siteId != $owner->siteId) { |
||
| 277 | return; |
||
| 278 | } |
||
| 279 | |||
| 280 | if (null === ($elements = $query->getCachedResult())) { |
||
| 281 | $query = clone $query; |
||
| 282 | $query->status = null; |
||
| 283 | $query->enabledForSite = false; |
||
| 284 | $elements = $query->all(); // existing meta |
||
| 285 | } |
||
| 286 | |||
| 287 | $transaction = Craft::$app->getDb()->beginTransaction(); |
||
| 288 | try { |
||
| 289 | // If this is a preexisting element, make sure that the blocks for this field/owner respect the |
||
| 290 | // field's translation setting |
||
| 291 | if ($query->ownerId) { |
||
| 292 | $this->applyFieldTranslationSetting($query->ownerId, $query->siteId, $field); |
||
| 293 | } |
||
| 294 | |||
| 295 | // If the query is set to fetch blocks of a different owner, we're probably duplicating an element |
||
| 296 | if ($query->ownerId && $query->ownerId != $owner->id) { |
||
| 297 | $this->duplicate($field, $owner, $query, $elements); |
||
| 298 | } else { |
||
| 299 | $this->save($field, $owner, $elements); |
||
| 300 | } |
||
| 301 | |||
| 302 | $transaction->commit(); |
||
| 303 | } catch (\Exception $e) { |
||
| 304 | $transaction->rollback(); |
||
| 305 | throw $e; |
||
| 306 | } |
||
| 307 | |||
| 308 | return; |
||
| 309 | } |
||
| 310 | |||
| 311 | /** |
||
| 312 | * @param MetaField $field |
||
| 313 | * @param ElementInterface $owner |
||
| 314 | * @param MetaQuery $query |
||
| 315 | * @param array $elements |
||
| 316 | * @throws \Throwable |
||
| 317 | * @throws \craft\errors\InvalidElementException |
||
| 318 | */ |
||
| 319 | private function duplicate(MetaField $field, ElementInterface $owner, MetaQuery $query, array $elements) |
||
| 320 | { |
||
| 321 | /** @var Element $owner */ |
||
| 322 | |||
| 323 | $newQuery = clone $query; |
||
| 324 | $newQuery->ownerId = $owner->id; |
||
| 325 | if (!$newQuery->exists()) { |
||
| 326 | // Duplicate for the new owner |
||
| 327 | $elementsService = Craft::$app->getElements(); |
||
| 328 | foreach ($elements as $element) { |
||
| 329 | $elementsService->duplicateElement($element, [ |
||
| 330 | 'ownerId' => $owner->id, |
||
| 331 | 'ownerSiteId' => $field->localize ? $owner->siteId : null |
||
| 332 | ]); |
||
| 333 | } |
||
| 334 | } |
||
| 335 | } |
||
| 336 | |||
| 337 | /** |
||
| 338 | * @param MetaField $field |
||
| 339 | * @param ElementInterface $owner |
||
| 340 | * @param MetaElement[] $elements |
||
| 341 | * @throws Exception |
||
| 342 | * @throws \Throwable |
||
| 343 | * @throws \craft\errors\ElementNotFoundException |
||
| 344 | */ |
||
| 345 | private function save(MetaField $field, ElementInterface $owner, array $elements) |
||
| 346 | { |
||
| 347 | /** @var Element $owner */ |
||
| 348 | |||
| 349 | $elementIds = []; |
||
| 350 | |||
| 351 | // Only propagate the blocks if the owner isn't being propagated |
||
| 352 | $propagate = !$owner->propagating; |
||
| 353 | |||
| 354 | /** @var MetaElement $element */ |
||
| 355 | foreach ($elements as $element) { |
||
| 356 | $element->setOwnerId($owner->id); |
||
| 357 | $element->ownerSiteId = ($field->localize ? $owner->siteId : null); |
||
| 358 | $element->propagating = $owner->propagating; |
||
| 359 | |||
| 360 | Craft::$app->getElements()->saveElement($element, false, $propagate); |
||
| 361 | |||
| 362 | $elementIds[] = $element->id; |
||
| 363 | } |
||
| 364 | |||
| 365 | // Delete any elements that have been removed |
||
| 366 | $this->deleteOld($field, $owner, $elementIds); |
||
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * @param MetaField $field |
||
| 371 | * @param ElementInterface $owner |
||
| 372 | * @param array $excludeIds |
||
| 373 | * @throws \Throwable |
||
| 374 | */ |
||
| 375 | private function deleteOld(MetaField $field, ElementInterface $owner, array $excludeIds) |
||
| 376 | { |
||
| 377 | /** @var Element $owner */ |
||
| 378 | |||
| 379 | $deleteElementsQuery = MetaElement::find() |
||
| 380 | ->status(null) |
||
| 381 | ->enabledForSite(false) |
||
| 382 | ->ownerId($owner->id) |
||
| 383 | ->fieldId($field->id) |
||
| 384 | ->where(['not', ['elements.id' => $excludeIds]]); |
||
| 385 | |||
| 386 | if ($field->localize) { |
||
| 387 | $deleteElementsQuery->ownerSiteId($owner->siteId); |
||
| 388 | } else { |
||
| 389 | $deleteElementsQuery->siteId($owner->siteId); |
||
| 390 | } |
||
| 391 | |||
| 392 | foreach ($deleteElementsQuery->all() as $deleteElement) { |
||
| 393 | Craft::$app->getElements()->deleteElement($deleteElement); |
||
| 394 | } |
||
| 395 | } |
||
| 396 | |||
| 397 | /** |
||
| 398 | * Applies the field's translation setting to a set of blocks. |
||
| 399 | * |
||
| 400 | * @param int $ownerId |
||
| 401 | * @param int $ownerSiteId |
||
| 402 | * @param Meta $field |
||
| 403 | * @throws Exception |
||
| 404 | * @throws \Throwable |
||
| 405 | * @throws \craft\errors\ElementNotFoundException |
||
| 406 | */ |
||
| 407 | private function applyFieldTranslationSetting(int $ownerId, int $ownerSiteId, MetaField $field) |
||
| 408 | { |
||
| 409 | // If the field is translatable, see if there are any global blocks that should be localized |
||
| 410 | if ($field->localize) { |
||
| 411 | $this->saveFieldTranslations($field, $ownerId, $ownerSiteId); |
||
| 412 | } else { |
||
| 413 | // Otherwise, see if the field has any localized blocks that should be deleted |
||
| 414 | foreach (Craft::$app->getSites()->getAllSiteIds() as $siteId) { |
||
| 415 | if ($siteId != $ownerSiteId) { |
||
| 416 | /** @var MetaQuery $elements */ |
||
| 417 | $elements = MetaElement::find() |
||
| 418 | ->fieldId($field->id) |
||
| 419 | ->ownerId($ownerId) |
||
| 420 | ->status(null) |
||
| 421 | ->enabledForSite(false) |
||
| 422 | ->limit(null) |
||
| 423 | ->siteId($siteId) |
||
| 424 | ->ownerSiteId($siteId) |
||
| 425 | ->all(); |
||
| 426 | |||
| 427 | foreach ($elements as $element) { |
||
| 428 | Craft::$app->getElements()->deleteElement($element); |
||
| 429 | } |
||
| 430 | } |
||
| 431 | } |
||
| 432 | } |
||
| 433 | } |
||
| 434 | |||
| 435 | /** |
||
| 436 | * @param MetaField $field |
||
| 437 | * @param int $ownerId |
||
| 438 | * @param int $ownerSiteId |
||
| 439 | * @throws Exception |
||
| 440 | * @throws \Throwable |
||
| 441 | * @throws \craft\errors\ElementNotFoundException |
||
| 442 | */ |
||
| 443 | private function saveFieldTranslations(MetaField $field, int $ownerId, int $ownerSiteId) |
||
| 481 | |||
| 482 | /** |
||
| 483 | * @param MetaQuery $query |
||
| 484 | * @param int $ownerSiteId |
||
| 485 | * @return array |
||
| 486 | */ |
||
| 487 | private function getOtherSiteMeta(MetaQuery $query, int $ownerSiteId) |
||
| 511 | |||
| 512 | /** |
||
| 513 | * @param array $elements |
||
| 514 | * @return array |
||
| 515 | */ |
||
| 516 | private function getRelationFields(array $elements) |
||
| 532 | |||
| 533 | /** |
||
| 534 | * @inheritdoc |
||
| 535 | */ |
||
| 536 | protected function normalizeQueryInputValue( |
||
| 545 | } |
||
| 546 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.