Complex classes like Relation 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 Relation, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | abstract class Relation |
||
33 | { |
||
34 | const HAS = 'has'; |
||
35 | const HAS_MANY = 'has_many'; |
||
36 | const BELONGS_TO = 'belongs_to'; |
||
37 | const BELONGS_TO_MANY = 'belongs_to_many'; |
||
38 | |||
39 | /** |
||
40 | * A map of relation type constants to their respective implementations. |
||
41 | * |
||
42 | * @var array |
||
43 | */ |
||
44 | protected static $classMap = array( |
||
45 | self::HAS => 'Darya\ORM\Relation\Has', |
||
46 | self::HAS_MANY => 'Darya\ORM\Relation\HasMany', |
||
47 | self::BELONGS_TO => 'Darya\ORM\Relation\BelongsTo', |
||
48 | self::BELONGS_TO_MANY => 'Darya\ORM\Relation\BelongsToMany', |
||
49 | ); |
||
50 | |||
51 | /** |
||
52 | * The name of the relation in the context of the parent model. |
||
53 | * |
||
54 | * @var string |
||
55 | */ |
||
56 | protected $name = ''; |
||
57 | |||
58 | /** |
||
59 | * The parent model. |
||
60 | * |
||
61 | * @var Record |
||
62 | */ |
||
63 | protected $parent; |
||
64 | |||
65 | /** |
||
66 | * The target model. |
||
67 | * |
||
68 | * @var Record |
||
69 | */ |
||
70 | protected $target; |
||
71 | |||
72 | /** |
||
73 | * Foreign key on the "belongs-to" model. |
||
74 | * |
||
75 | * @var string |
||
76 | */ |
||
77 | protected $foreignKey; |
||
78 | |||
79 | /** |
||
80 | * Local key on the "has" model. |
||
81 | * |
||
82 | * @var string |
||
83 | */ |
||
84 | protected $localKey; |
||
85 | |||
86 | /** |
||
87 | * Filter for constraining related models loaded from storage. |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | protected $constraint = array(); |
||
92 | |||
93 | /** |
||
94 | * Sort order for related models. |
||
95 | * |
||
96 | * @var array |
||
97 | */ |
||
98 | protected $sort = array(); |
||
99 | |||
100 | /** |
||
101 | * The related instances. |
||
102 | * |
||
103 | * @var Record[] |
||
104 | */ |
||
105 | protected $related = array(); |
||
106 | |||
107 | /** |
||
108 | * Detached instances that need dissociating on save. |
||
109 | * |
||
110 | * @var Record[] |
||
111 | */ |
||
112 | protected $detached = array(); |
||
113 | |||
114 | /** |
||
115 | * Determines whether related instances have been loaded. |
||
116 | * |
||
117 | * @var bool |
||
118 | */ |
||
119 | protected $loaded = false; |
||
120 | |||
121 | /** |
||
122 | * The storage interface. |
||
123 | * |
||
124 | * @var Queryable |
||
125 | */ |
||
126 | protected $storage; |
||
127 | |||
128 | /** |
||
129 | * Helper method for methods that accept single or multiple values, or for |
||
130 | * just casting to an array without losing a plain object. |
||
131 | * |
||
132 | * Returns an array with the given value as its sole element, if it is not |
||
133 | * an array already. |
||
134 | * |
||
135 | * This exists because casting an object to an array results in its public |
||
136 | * properties being set as the values. |
||
137 | * |
||
138 | * @param mixed $value |
||
139 | * @return array |
||
140 | */ |
||
141 | protected static function arrayify($value) |
||
145 | |||
146 | /** |
||
147 | * Separate array elements with numeric keys from those with string keys. |
||
148 | * |
||
149 | * @param array $array |
||
150 | * @return array array($numeric, $strings) |
||
151 | */ |
||
152 | protected static function separateKeys(array $array) |
||
167 | |||
168 | /** |
||
169 | * Resolve a relation class name from the given relation type constant. |
||
170 | * |
||
171 | * @param string $type |
||
172 | * @return string |
||
173 | */ |
||
174 | protected static function resolveClass($type) |
||
182 | |||
183 | /** |
||
184 | * Create a new relation of the given type using the given arguments. |
||
185 | * |
||
186 | * Applies numerically-keyed arguments to the constructor and string-keyed |
||
187 | * arguments to methods with the same name. |
||
188 | * |
||
189 | * @param string $type |
||
190 | * @param array $arguments |
||
191 | * @return Relation |
||
192 | */ |
||
193 | public static function factory($type = self::HAS, array $arguments) |
||
215 | |||
216 | /** |
||
217 | * Instantiate a new relation. |
||
218 | * |
||
219 | * @param Record $parent Parent class |
||
220 | * @param string $target Related class that extends \Darya\ORM\Record |
||
221 | * @param string $foreignKey [optional] Custom foreign key |
||
222 | * @param array $constraint [optional] Constraint filter for related models |
||
223 | * @throws InvalidArgumentException |
||
224 | */ |
||
225 | public function __construct(Record $parent, $target, $foreignKey = null, array $constraint = array()) |
||
238 | |||
239 | /** |
||
240 | * Lowercase and delimit the given PascalCase class name. |
||
241 | * |
||
242 | * @param string $class |
||
243 | * @return string |
||
244 | */ |
||
245 | protected function delimitClass($class) |
||
254 | |||
255 | /** |
||
256 | * Prepare a foreign key from the given class name. |
||
257 | * |
||
258 | * @param string $class |
||
259 | * @return string |
||
260 | */ |
||
261 | protected function prepareForeignKey($class) |
||
265 | |||
266 | /** |
||
267 | * Retrieve the default filter for the related models. |
||
268 | * |
||
269 | * @return array |
||
270 | */ |
||
271 | protected function defaultConstraint() |
||
277 | |||
278 | /** |
||
279 | * Set the default keys for the relation if they haven't already been set. |
||
280 | */ |
||
281 | abstract protected function setDefaultKeys(); |
||
282 | |||
283 | /** |
||
284 | * Retrieve the values of the given attribute of the given instances. |
||
285 | * |
||
286 | * Works similarly to array_column(), but doesn't return data from any rows |
||
287 | * without the given attribute set. |
||
288 | * |
||
289 | * Optionally accepts a second attribute to index by. |
||
290 | * |
||
291 | * @param Record[]|Record|array $instances |
||
292 | * @param string $attribute |
||
293 | * @param string $index [optional] |
||
294 | * @return array |
||
295 | */ |
||
296 | protected static function attributeList($instances, $attribute, $index = null) |
||
312 | |||
313 | /** |
||
314 | * Build an adjacency list of related models, indexed by their foreign keys. |
||
315 | * |
||
316 | * Optionally accepts a different attribute to index the models by. |
||
317 | * |
||
318 | * @param Record[] $instances |
||
319 | * @param string $index [optional] |
||
320 | * @return array |
||
321 | */ |
||
322 | protected function adjacencyList(array $instances, $index = null) |
||
334 | |||
335 | /** |
||
336 | * Reduce the cached related models to those with the given IDs. |
||
337 | * |
||
338 | * If no IDs are given then all of the in-memory models will be removed. |
||
339 | * |
||
340 | * @param int[] $ids |
||
341 | */ |
||
342 | protected function reduce(array $ids = array()) |
||
358 | |||
359 | /** |
||
360 | * Replace a cached related model. |
||
361 | * |
||
362 | * If the related model does not have an ID or it is not found, it is simply |
||
363 | * appended. |
||
364 | * |
||
365 | * TODO: Remove from $this->detached if found? |
||
366 | * |
||
367 | * @param Record $instance |
||
368 | */ |
||
369 | protected function replace(Record $instance) |
||
389 | |||
390 | /** |
||
391 | * Save the given record to storage if it hasn't got an ID. |
||
392 | * |
||
393 | * @param Record $instance |
||
394 | */ |
||
395 | protected function persist(Record $instance) |
||
401 | |||
402 | /** |
||
403 | * Verify that the given models are instances of the relation's target |
||
404 | * class. |
||
405 | * |
||
406 | * Throws an exception if any of them aren't. |
||
407 | * |
||
408 | * @param Record[]|Record $instances |
||
409 | * @throws Exception |
||
410 | */ |
||
411 | protected function verify($instances) |
||
415 | |||
416 | /** |
||
417 | * Verify that the given objects are instances of the given class. |
||
418 | * |
||
419 | * @param object[]|object $instances |
||
420 | * @param string $class |
||
421 | * @throws Exception |
||
422 | */ |
||
423 | protected static function verifyModels($instances, $class) |
||
435 | |||
436 | /** |
||
437 | * Verify that the given models are instances of the relation's parent |
||
438 | * class. |
||
439 | * |
||
440 | * Throws an exception if any of them aren't. |
||
441 | * |
||
442 | * @param Record[]|Record $instances |
||
443 | * @throws Exception |
||
444 | */ |
||
445 | protected function verifyParents($instances) |
||
449 | |||
450 | /** |
||
451 | * Retrieve and optionally set the storage used for the target model. |
||
452 | * |
||
453 | * Falls back to target model storage, then parent model storage. |
||
454 | * |
||
455 | * @param Queryable $storage |
||
456 | * @return Queryable |
||
457 | */ |
||
458 | public function storage(Queryable $storage = null) |
||
464 | |||
465 | /** |
||
466 | * Retrieve and optionally set the name of the relation on the parent model. |
||
467 | * |
||
468 | * @param string $name [optional] |
||
469 | * @return string |
||
470 | */ |
||
471 | public function name($name = '') |
||
477 | |||
478 | /** |
||
479 | * Retrieve and optionally set the foreign key for the "belongs-to" model. |
||
480 | * |
||
481 | * @param string $foreignKey [optional] |
||
482 | * @return string |
||
483 | */ |
||
484 | public function foreignKey($foreignKey = '') |
||
490 | |||
491 | /** |
||
492 | * Retrieve and optionally set the local key for the "has" model. |
||
493 | * |
||
494 | * @param string $localKey [optional] |
||
495 | * @return string |
||
496 | */ |
||
497 | public function localKey($localKey = '') |
||
503 | |||
504 | /** |
||
505 | * Set a filter to constrain which models are considered related. |
||
506 | * |
||
507 | * @param array $filter |
||
508 | */ |
||
509 | public function constrain(array $filter) |
||
513 | |||
514 | /** |
||
515 | * Retrieve the custom filter used to constrain related models. |
||
516 | * |
||
517 | * @return array |
||
518 | */ |
||
519 | public function constraint() |
||
523 | |||
524 | /** |
||
525 | * Retrieve the filter for this relation. |
||
526 | * |
||
527 | * @return array |
||
528 | */ |
||
529 | public function filter() |
||
533 | |||
534 | /** |
||
535 | * Set the sorting order for this relation. |
||
536 | * |
||
537 | * @param array|string $order |
||
538 | * @return array|string |
||
539 | */ |
||
540 | public function sort($order) |
||
544 | |||
545 | /** |
||
546 | * Retrieve the order for this relation. |
||
547 | * |
||
548 | * @return array|string |
||
549 | */ |
||
550 | public function order() |
||
554 | |||
555 | /** |
||
556 | * Read related model data from storage. |
||
557 | * |
||
558 | * TODO: $filter, $order, $offset |
||
559 | * |
||
560 | * @param int $limit [optional] |
||
561 | * @return array |
||
562 | */ |
||
563 | public function read($limit = 0) |
||
567 | |||
568 | /** |
||
569 | * Query related model data from storage. |
||
570 | * |
||
571 | * @return Builder |
||
572 | */ |
||
573 | public function query() |
||
587 | |||
588 | /** |
||
589 | * Read, generate and set cached related models from storage. |
||
590 | * |
||
591 | * This will completely replace any cached related models. |
||
592 | * |
||
593 | * @param int $limit [optional] |
||
594 | * @return Record[] |
||
595 | */ |
||
596 | public function load($limit = 0) |
||
605 | |||
606 | /** |
||
607 | * Determine whether cached related models have been loaded from storage. |
||
608 | * |
||
609 | * @return bool |
||
610 | */ |
||
611 | public function loaded() |
||
615 | |||
616 | /** |
||
617 | * Eagerly load the related models for the given parent instances. |
||
618 | * |
||
619 | * Returns the given instances with their related models loaded. |
||
620 | * |
||
621 | * @param array $instances |
||
622 | * @return array |
||
623 | */ |
||
624 | abstract public function eager(array $instances); |
||
625 | |||
626 | /** |
||
627 | * Retrieve one or many related model instances, depending on the relation. |
||
628 | * |
||
629 | * @return Record[]|Record|null |
||
630 | */ |
||
631 | abstract public function retrieve(); |
||
632 | |||
633 | /** |
||
634 | * Retrieve one related model instance. |
||
635 | * |
||
636 | * @return Record|null |
||
637 | */ |
||
638 | public function one() |
||
648 | |||
649 | /** |
||
650 | * Retrieve all related model instances. |
||
651 | * |
||
652 | * @return Record[]|null |
||
653 | */ |
||
654 | public function all() |
||
664 | |||
665 | /** |
||
666 | * Count the number of related model instances. |
||
667 | * |
||
668 | * Counts loaded or attached instances if they are present, queries storage |
||
669 | * otherwise. |
||
670 | * |
||
671 | * @return int |
||
672 | */ |
||
673 | public function count() |
||
681 | |||
682 | /** |
||
683 | * Set the related models. |
||
684 | * |
||
685 | * Overwrites any currently set related models. |
||
686 | * |
||
687 | * @param Record[] $instances |
||
688 | */ |
||
689 | public function set($instances) |
||
695 | |||
696 | /** |
||
697 | * Clear the related models. |
||
698 | */ |
||
699 | public function clear() |
||
704 | |||
705 | /** |
||
706 | * Attach the given models. |
||
707 | * |
||
708 | * @param Record[]|Record $instances |
||
709 | */ |
||
710 | public function attach($instances) |
||
718 | |||
719 | /** |
||
720 | * Detach the given models. |
||
721 | * |
||
722 | * Detaches all attached models if none are given. |
||
723 | * |
||
724 | * @param Record[]|Record $instances [optional] |
||
725 | */ |
||
726 | public function detach($instances = array()) |
||
750 | |||
751 | /** |
||
752 | * Associate the given models. |
||
753 | * |
||
754 | * Returns the number of models successfully associated. |
||
755 | * |
||
756 | * @param Record[]|Record $instances |
||
757 | * @return int |
||
758 | */ |
||
759 | abstract public function associate($instances); |
||
760 | |||
761 | /** |
||
762 | * Dissociate the given models. |
||
763 | * |
||
764 | * Returns the number of models successfully dissociated. |
||
765 | * |
||
766 | * @param Record[]|Record $instances [optional] |
||
767 | * @return int |
||
768 | */ |
||
769 | abstract public function dissociate($instances = array()); |
||
770 | |||
771 | /** |
||
772 | * Save the relationship. |
||
773 | * |
||
774 | * Associates related models and dissociates detached models. |
||
775 | * |
||
776 | * Optionally accepts a set of IDs to save by. Saves all related models |
||
777 | * otherwise. |
||
778 | * |
||
779 | * Returns the number of associated models. |
||
780 | * |
||
781 | * @param int[] $ids |
||
782 | * @return int |
||
783 | */ |
||
784 | public function save(array $ids = array()) |
||
821 | |||
822 | /** |
||
823 | * Dynamic read-only access for relation properties. |
||
824 | * |
||
825 | * @param string $property |
||
826 | * @return mixed |
||
827 | */ |
||
828 | public function __get($property) |
||
834 | } |
||
835 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.