Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Model 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 Model, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | abstract class Model implements Serializes { |
||
25 | /** |
||
26 | * Table attribute key. |
||
27 | */ |
||
28 | const TABLE_KEY = '@@table'; |
||
29 | |||
30 | /** |
||
31 | * Object attribute key. |
||
32 | */ |
||
33 | const OBJECT_KEY = '@@object'; |
||
34 | |||
35 | /** |
||
36 | * Memoized values for class methods. |
||
37 | * |
||
38 | * @var array |
||
39 | */ |
||
40 | private static $memo = array(); |
||
41 | |||
42 | /** |
||
43 | * Model attributes. |
||
44 | * |
||
45 | * @var array |
||
46 | */ |
||
47 | private $attributes = array( |
||
48 | self::TABLE_KEY => array(), |
||
49 | self::OBJECT_KEY => null, |
||
50 | ); |
||
51 | |||
52 | /** |
||
53 | * Model's original attributes. |
||
54 | * |
||
55 | * @var array |
||
56 | */ |
||
57 | private $original = array( |
||
58 | self::TABLE_KEY => array(), |
||
59 | self::OBJECT_KEY => null, |
||
60 | ); |
||
61 | |||
62 | /** |
||
63 | * Default attribute values. |
||
64 | * |
||
65 | * @var array |
||
66 | */ |
||
67 | protected $defaults = array(); |
||
68 | |||
69 | /** |
||
70 | * Properties which are allowed to be set on the model. |
||
71 | * |
||
72 | * If this array is empty, any attributes can be set on the model. |
||
73 | * |
||
74 | * @var string[] |
||
75 | */ |
||
76 | protected $fillable = array(); |
||
77 | |||
78 | /** |
||
79 | * Properties which cannot be automatically filled on the model. |
||
80 | * |
||
81 | * If the model is unguarded, these properties can be filled. |
||
82 | * |
||
83 | * @var array |
||
84 | */ |
||
85 | protected $guarded = array(); |
||
86 | |||
87 | /** |
||
88 | * Properties which should not be serialized. |
||
89 | * |
||
90 | * @var array |
||
91 | */ |
||
92 | protected $hidden = array(); |
||
93 | |||
94 | /** |
||
95 | * Properties which should be serialized. |
||
96 | * |
||
97 | * @var array |
||
98 | */ |
||
99 | protected $visible = array(); |
||
100 | |||
101 | /** |
||
102 | * Whether the model's properties are guarded. |
||
103 | * |
||
104 | * When false, allows guarded properties to be filled. |
||
105 | * |
||
106 | * @var bool |
||
107 | */ |
||
108 | protected $is_guarded = true; |
||
109 | |||
110 | /** |
||
111 | * Constructs a new model with provided attributes. |
||
112 | * |
||
113 | * If self::OBJECT_KEY is passed as one of the attributes, the underlying post |
||
114 | * will be overwritten. |
||
115 | * |
||
116 | * @param array <string, mixed> $attributes |
||
117 | */ |
||
118 | 96 | public function __construct( array $attributes = array() ) { |
|
130 | |||
131 | /** |
||
132 | * Refreshes the model's current attributes with the provided array. |
||
133 | * |
||
134 | * The model's attributes will match what was provided in the array, |
||
135 | * and any attributes not passed |
||
136 | * |
||
137 | * @param array $attributes |
||
138 | * |
||
139 | * @return $this |
||
140 | */ |
||
141 | 96 | public function refresh( array $attributes ) { |
|
146 | |||
147 | /** |
||
148 | * Merges the provided attributes with the provided array. |
||
149 | * |
||
150 | * @param array $attributes |
||
151 | * |
||
152 | * @return $this |
||
153 | */ |
||
154 | 96 | public function merge( array $attributes ) { |
|
161 | |||
162 | /** |
||
163 | * Get the model's table attributes. |
||
164 | * |
||
165 | * Returns the array of for the model that will either need to be |
||
166 | * saved in postmeta or a separate table. |
||
167 | * |
||
168 | * @return array |
||
169 | */ |
||
170 | 12 | public function get_table_attributes() { |
|
173 | |||
174 | /** |
||
175 | * Get the model's original attributes. |
||
176 | * |
||
177 | * @return array |
||
178 | */ |
||
179 | 6 | public function get_original_table_attributes() { |
|
182 | |||
183 | /** |
||
184 | * Retrieve an array of the attributes on the model |
||
185 | * that have changed compared to the model's |
||
186 | * original data. |
||
187 | * |
||
188 | * @return array |
||
189 | */ |
||
190 | 3 | public function get_changed_table_attributes() { |
|
203 | |||
204 | /** |
||
205 | * Get the model's underlying post. |
||
206 | * |
||
207 | * Returns the underlying WP_Post object for the model, representing |
||
208 | * the data that will be save in the wp_posts table. |
||
209 | * |
||
210 | * @return false|WP_Post|WP_Term |
||
211 | */ |
||
212 | 18 | public function get_underlying_wp_object() { |
|
219 | |||
220 | /** |
||
221 | * Get the model's original underlying post. |
||
222 | * |
||
223 | * @return WP_Post |
||
224 | */ |
||
225 | 6 | public function get_original_underlying_wp_object() { |
|
228 | |||
229 | /** |
||
230 | * Get the model attributes on the WordPress object |
||
231 | * that have changed compared to the model's |
||
232 | * original attributes. |
||
233 | * |
||
234 | * @return array |
||
235 | */ |
||
236 | 3 | public function get_changed_wp_object_attributes() { |
|
249 | |||
250 | /** |
||
251 | * Magic __set method. |
||
252 | * |
||
253 | * Passes the name and value to set_attribute, which is where the magic happens. |
||
254 | * |
||
255 | * @param string $name |
||
256 | * @param mixed $value |
||
257 | */ |
||
258 | 6 | public function __set( $name, $value ) { |
|
261 | |||
262 | /** |
||
263 | * Sets the model attributes. |
||
264 | * |
||
265 | * Checks whether the model attribute can be set, check if it |
||
266 | * maps to the WP_Post property, otherwise, assigns it to the |
||
267 | * table attribute array. |
||
268 | * |
||
269 | * @param string $name |
||
270 | * @param mixed $value |
||
271 | * |
||
272 | * @return $this |
||
273 | * |
||
274 | * @throws GuardedPropertyException |
||
275 | */ |
||
276 | 96 | public function set_attribute( $name, $value ) { |
|
297 | |||
298 | /** |
||
299 | * Retrieves all the attribute keys for the model. |
||
300 | * |
||
301 | * @return array |
||
302 | */ |
||
303 | 18 | public function get_attribute_keys() { |
|
315 | |||
316 | /** |
||
317 | * Retrieves the attribute keys that aren't mapped to a post. |
||
318 | * |
||
319 | * @return array |
||
320 | */ |
||
321 | 96 | View Code Duplication | public function get_table_keys() { |
338 | |||
339 | /** |
||
340 | * Retrieves the attribute keys that are mapped to a post. |
||
341 | * |
||
342 | * @return array |
||
343 | */ |
||
344 | 96 | View Code Duplication | public function get_wp_object_keys() { |
359 | |||
360 | /** |
||
361 | * Returns the model's keys that are computed at call time. |
||
362 | * |
||
363 | * @return array |
||
364 | */ |
||
365 | 3 | View Code Duplication | public function get_computed_keys() { |
380 | |||
381 | /** |
||
382 | * Serializes the model's public data into an array. |
||
383 | * |
||
384 | * @return array |
||
385 | */ |
||
386 | 12 | public function serialize() { |
|
416 | |||
417 | /** |
||
418 | * Syncs the current attributes to the model's original. |
||
419 | * |
||
420 | * @return $this |
||
421 | */ |
||
422 | 96 | public function sync_original() { |
|
437 | |||
438 | /** |
||
439 | * Checks if a given attribute is mass-fillable. |
||
440 | * |
||
441 | * Returns true if the attribute can be filled, false if it can't. |
||
442 | * |
||
443 | * @param string $name |
||
444 | * |
||
445 | * @return bool |
||
446 | */ |
||
447 | 96 | private function is_fillable( $name ) { |
|
466 | |||
467 | /** |
||
468 | * Overrides the current WordPress object with a provided one. |
||
469 | * |
||
470 | * Resets the post's default values and stores it in the attributes. |
||
471 | * |
||
472 | * @param WP_Post|WP_Term|null $value |
||
473 | * |
||
474 | * @return $this |
||
475 | */ |
||
476 | 27 | private function override_wp_object( $value ) { |
|
489 | |||
490 | /** |
||
491 | * Overrides the current table attributes array with a provided one. |
||
492 | * |
||
493 | * @param array $value |
||
494 | * |
||
495 | * @return $this |
||
496 | */ |
||
497 | 6 | private function override_table( array $value ) { |
|
502 | |||
503 | /** |
||
504 | * Create and set with a new blank post. |
||
505 | * |
||
506 | * Creates a new WP_Post object, assigns it the default attributes, |
||
507 | * and stores it in the attributes. |
||
508 | * |
||
509 | * @throws LogicException |
||
510 | */ |
||
511 | 60 | private function create_wp_object() { |
|
526 | |||
527 | /** |
||
528 | * Enforces values on the post that can't change. |
||
529 | * |
||
530 | * Primarily, this is used to make sure the post_type always maps |
||
531 | * to the model's "$type" property, but this can all be overridden |
||
532 | * by the developer to enforce other values in the model. |
||
533 | * |
||
534 | * @param object $object |
||
535 | * |
||
536 | * @return object |
||
537 | */ |
||
538 | 60 | protected function set_wp_object_constants( $object ) { |
|
549 | |||
550 | /** |
||
551 | * Magic __get method. |
||
552 | * |
||
553 | * Passes the name and value to get_attribute, which is where the magic happens. |
||
554 | * |
||
555 | * @param string $name |
||
556 | * |
||
557 | * @return mixed |
||
558 | */ |
||
559 | 24 | public function __get( $name ) { |
|
562 | |||
563 | /** |
||
564 | * Retrieves the model attribute. |
||
565 | * |
||
566 | * @param string $name |
||
567 | * |
||
568 | * @return mixed |
||
569 | * |
||
570 | * @throws PropertyDoesNotExistException If property isn't found. |
||
571 | */ |
||
572 | 51 | public function get_attribute( $name ) { |
|
591 | |||
592 | /** |
||
593 | * Retrieve the model's original attribute value. |
||
594 | * |
||
595 | * @param string $name |
||
596 | * |
||
597 | * @return mixed |
||
598 | * |
||
599 | * @throws PropertyDoesNotExistException If property isn't found. |
||
600 | */ |
||
601 | 6 | public function get_original_attribute( $name ) { |
|
612 | |||
613 | /** |
||
614 | * Fetches the Model's primary ID, depending on the model |
||
615 | * implementation. |
||
616 | * |
||
617 | * @return int |
||
618 | * |
||
619 | * @throws LogicException |
||
620 | */ |
||
621 | public function get_primary_id() { |
||
637 | |||
638 | /** |
||
639 | * Checks whether the attribute has a map method. |
||
640 | * |
||
641 | * This is used to determine whether the attribute maps to a |
||
642 | * property on the underlying WP_Post object. Returns the |
||
643 | * method if one exists, returns false if it doesn't. |
||
644 | * |
||
645 | * @param string $name |
||
646 | * |
||
647 | * @return false|string |
||
648 | */ |
||
649 | 96 | protected function has_map_method( $name ) { |
|
656 | |||
657 | /** |
||
658 | * Checks whether the attribute has a compute method. |
||
659 | * |
||
660 | * This is used to determine if the attribute should be computed |
||
661 | * from other attributes. |
||
662 | * |
||
663 | * @param string $name |
||
664 | * |
||
665 | * @return false|string |
||
666 | */ |
||
667 | 51 | protected function has_compute_method( $name ) { |
|
674 | |||
675 | /** |
||
676 | * Clears all the current attributes from the model. |
||
677 | * |
||
678 | * This does not touch the model's original attributes, and will |
||
679 | * only clear fillable attributes, unless the model is unguarded. |
||
680 | * |
||
681 | * @throws Exception |
||
682 | * @return $this |
||
683 | */ |
||
684 | 96 | public function clear() { |
|
703 | |||
704 | /** |
||
705 | * Unguards the model. |
||
706 | * |
||
707 | * Sets the model to be unguarded, allowing the filling of |
||
708 | * guarded attributes. |
||
709 | */ |
||
710 | 96 | public function unguard() { |
|
713 | |||
714 | /** |
||
715 | * Reguards the model. |
||
716 | * |
||
717 | * Sets the model to be guarded, preventing filling of |
||
718 | * guarded attributes. |
||
719 | */ |
||
720 | 96 | public function reguard() { |
|
723 | |||
724 | /** |
||
725 | * Retrieves all the compute methods on the model. |
||
726 | * |
||
727 | * @return array |
||
728 | */ |
||
729 | 9 | protected function get_compute_methods() { |
|
730 | 9 | $methods = get_class_methods( get_called_class() ); |
|
731 | 3 | $methods = array_filter( $methods, function ( $method ) { |
|
732 | 9 | return strrpos( $method, 'compute_', - strlen( $method ) ) !== false; |
|
733 | 9 | } ); |
|
734 | 9 | $methods = array_map( function ( $method ) { |
|
735 | 6 | return substr( $method, strlen( 'compute_' ) ); |
|
736 | 9 | }, $methods ); |
|
737 | |||
738 | 9 | return $methods; |
|
739 | } |
||
740 | |||
741 | /** |
||
742 | * Sets up the memo array for the creating model. |
||
743 | */ |
||
744 | 96 | private function maybe_boot() { |
|
749 | |||
750 | /** |
||
751 | * Whether this Model uses an underlying WordPress object. |
||
752 | * |
||
753 | * @return bool |
||
754 | */ |
||
755 | 96 | protected function uses_wp_object() { |
|
759 | } |
||
760 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.