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() ) { |
|
119 | 96 | $this->maybe_boot(); |
|
120 | 96 | $this->sync_original(); |
|
121 | |||
122 | 96 | if ( $this->uses_wp_object() ) { |
|
123 | 60 | $this->create_wp_object(); |
|
124 | 40 | } |
|
125 | |||
126 | 96 | $this->unguard(); |
|
127 | 96 | $this->refresh( $attributes ); |
|
128 | 96 | $this->reguard(); |
|
129 | 96 | } |
|
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 ) { |
|
142 | 96 | $this->clear(); |
|
143 | |||
144 | 96 | return $this->merge( $attributes ); |
|
145 | } |
||
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 ) { |
|
155 | 96 | foreach ( $attributes as $name => $value ) { |
|
156 | 45 | $this->set_attribute( $name, $value ); |
|
157 | 64 | } |
|
158 | |||
159 | 96 | return $this; |
|
160 | } |
||
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() { |
|
171 | 12 | return $this->attributes[ self::TABLE_KEY ]; |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * Get the model's original attributes. |
||
176 | * |
||
177 | * @return array |
||
178 | */ |
||
179 | 6 | public function get_original_table_attributes() { |
|
180 | 6 | return $this->original[ self::TABLE_KEY ]; |
|
181 | } |
||
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() { |
|
191 | 3 | $changed = array(); |
|
192 | |||
193 | 3 | foreach ( $this->get_table_attributes() as $key => $value ) { |
|
194 | if ( $value !== |
||
195 | 3 | $this->get_original_attribute( $key ) |
|
196 | 2 | ) { |
|
197 | 3 | $changed[ $key ] = $value; |
|
198 | 2 | } |
|
199 | 2 | } |
|
200 | |||
201 | 3 | return $changed; |
|
202 | } |
||
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() { |
|
213 | 18 | if ( isset( $this->attributes[ self::OBJECT_KEY ] ) ) { |
|
214 | 15 | return $this->attributes[ self::OBJECT_KEY ]; |
|
215 | } |
||
216 | |||
217 | 3 | return false; |
|
218 | } |
||
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() { |
|
226 | 6 | return $this->original[ self::OBJECT_KEY ]; |
|
227 | } |
||
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() { |
|
237 | 3 | $changed = array(); |
|
238 | |||
239 | 3 | foreach ( $this->get_wp_object_keys() as $key ) { |
|
240 | 3 | if ( $this->get_attribute( $key ) !== |
|
241 | 3 | $this->get_original_attribute( $key ) |
|
242 | 2 | ) { |
|
243 | 3 | $changed[ $key ] = $this->get_attribute( $key ); |
|
244 | 2 | } |
|
245 | 2 | } |
|
246 | |||
247 | 3 | return $changed; |
|
248 | } |
||
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 ) { |
|
259 | 6 | $this->set_attribute( $name, $value ); |
|
260 | 6 | } |
|
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 ) { |
|
277 | 96 | if ( self::OBJECT_KEY === $name ) { |
|
278 | 27 | return $this->override_wp_object( $value ); |
|
279 | } |
||
280 | |||
281 | 96 | if ( self::TABLE_KEY === $name ) { |
|
282 | 6 | return $this->override_table( $value ); |
|
283 | } |
||
284 | |||
285 | 96 | if ( ! $this->is_fillable( $name ) ) { |
|
286 | 9 | throw new GuardedPropertyException; |
|
287 | } |
||
288 | |||
289 | 96 | if ( $method = $this->has_map_method( $name ) ) { |
|
290 | 60 | $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()} = $value; |
|
291 | 40 | } else { |
|
292 | 96 | $this->attributes[ self::TABLE_KEY ][ $name ] = $value; |
|
293 | } |
||
294 | |||
295 | 96 | return $this; |
|
296 | } |
||
297 | |||
298 | /** |
||
299 | * Retrieves all the attribute keys for the model. |
||
300 | * |
||
301 | * @return array |
||
302 | */ |
||
303 | 18 | public function get_attribute_keys() { |
|
304 | 18 | if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) { |
|
305 | 18 | return self::$memo[ get_called_class() ][ __METHOD__ ]; |
|
306 | } |
||
307 | |||
308 | 9 | return self::$memo[ get_called_class() ][ __METHOD__ ] |
|
309 | 9 | = array_merge( |
|
310 | 9 | $this->fillable, |
|
311 | 9 | $this->guarded, |
|
312 | 9 | $this->get_compute_methods() |
|
313 | 6 | ); |
|
314 | } |
||
315 | |||
316 | /** |
||
317 | * Retrieves the attribute keys that aren't mapped to a post. |
||
318 | * |
||
319 | * @return array |
||
320 | */ |
||
321 | 96 | public function get_table_keys() { |
|
322 | 96 | if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) { |
|
323 | 93 | return self::$memo[ get_called_class() ][ __METHOD__ ]; |
|
324 | } |
||
325 | |||
326 | 9 | $keys = array(); |
|
327 | |||
328 | 9 | foreach ( $this->get_attribute_keys() as $key ) { |
|
329 | 9 | if ( ! $this->has_map_method( $key ) && |
|
330 | 9 | ! $this->has_compute_method( $key ) |
|
331 | 6 | ) { |
|
332 | 9 | $keys[] = $key; |
|
333 | 6 | } |
|
334 | 6 | } |
|
335 | |||
336 | 9 | return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys; |
|
337 | } |
||
338 | |||
339 | /** |
||
340 | * Retrieves the attribute keys that are mapped to a post. |
||
341 | * |
||
342 | * @return array |
||
343 | */ |
||
344 | 96 | public function get_wp_object_keys() { |
|
345 | 96 | if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) { |
|
346 | 93 | return self::$memo[ get_called_class() ][ __METHOD__ ]; |
|
347 | } |
||
348 | |||
349 | 9 | $keys = array(); |
|
350 | |||
351 | 9 | foreach ( $this->get_attribute_keys() as $key ) { |
|
352 | 9 | if ( $this->has_map_method( $key ) ) { |
|
353 | 7 | $keys[] = $key; |
|
354 | 4 | } |
|
355 | 6 | } |
|
356 | |||
357 | 9 | return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys; |
|
358 | } |
||
359 | |||
360 | /** |
||
361 | * Returns the model's keys that are computed at call time. |
||
362 | * |
||
363 | * @return array |
||
364 | */ |
||
365 | 3 | public function get_computed_keys() { |
|
366 | 3 | if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) { |
|
367 | 3 | return self::$memo[ get_called_class() ][ __METHOD__ ]; |
|
368 | } |
||
369 | |||
370 | 3 | $keys = array(); |
|
371 | |||
372 | 3 | foreach ( $this->get_attribute_keys() as $key ) { |
|
373 | 3 | if ( $this->has_compute_method( $key ) ) { |
|
374 | 3 | $keys[] = $key; |
|
375 | 2 | } |
|
376 | 2 | } |
|
377 | |||
378 | 3 | return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys; |
|
379 | } |
||
380 | |||
381 | /** |
||
382 | * Serializes the model's public data into an array. |
||
383 | * |
||
384 | * @return array |
||
385 | */ |
||
386 | 12 | public function serialize() { |
|
387 | 12 | $attributes = array(); |
|
388 | |||
389 | 12 | if ( $this->visible ) { |
|
390 | // If visible attributes are set, we'll only reveal those. |
||
391 | 6 | foreach ( $this->visible as $key ) { |
|
392 | 6 | $attributes[ $key ] = $this->get_attribute( $key ); |
|
393 | 4 | } |
|
394 | 10 | } elseif ( $this->hidden ) { |
|
395 | // If hidden attributes are set, we'll grab everything and hide those. |
||
396 | 3 | foreach ( $this->get_attribute_keys() as $key ) { |
|
397 | 3 | if ( ! in_array( $key, $this->hidden ) ) { |
|
398 | 3 | $attributes[ $key ] = $this->get_attribute( $key ); |
|
399 | 2 | } |
|
400 | 2 | } |
|
401 | 2 | } else { |
|
402 | // If nothing is hidden/visible, we'll grab and reveal everything. |
||
403 | 3 | foreach ( $this->get_attribute_keys() as $key ) { |
|
404 | 3 | $attributes[ $key ] = $this->get_attribute( $key ); |
|
405 | 2 | } |
|
406 | } |
||
407 | |||
408 | 4 | return array_map( function ( $attribute ) { |
|
409 | 12 | if ( $attribute instanceof Serializes ) { |
|
410 | 3 | return $attribute->serialize(); |
|
411 | } |
||
412 | |||
413 | 12 | return $attribute; |
|
414 | 12 | }, $attributes ); |
|
415 | } |
||
416 | |||
417 | /** |
||
418 | * Syncs the current attributes to the model's original. |
||
419 | * |
||
420 | * @return $this |
||
421 | */ |
||
422 | 96 | public function sync_original() { |
|
423 | 96 | $this->original = $this->attributes; |
|
424 | |||
425 | 96 | if ( $this->attributes[ self::OBJECT_KEY ] ) { |
|
426 | 9 | $this->original[ self::OBJECT_KEY ] = clone $this->attributes[ self::OBJECT_KEY ]; |
|
427 | 6 | } |
|
428 | |||
429 | 96 | foreach ( $this->original[ self::TABLE_KEY ] as $key => $item ) { |
|
430 | 9 | if ( is_object( $item ) ) { |
|
431 | 9 | $this->original[ $key ] = clone $item; |
|
432 | 6 | } |
|
433 | 64 | } |
|
434 | |||
435 | 96 | return $this; |
|
436 | } |
||
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 ) { |
|
448 | // If this model isn't guarded, everything is fillable. |
||
449 | 96 | if ( ! $this->is_guarded ) { |
|
450 | 96 | return true; |
|
451 | } |
||
452 | |||
453 | // If it's in the fillable array, then it's fillable. |
||
454 | 18 | if ( in_array( $name, $this->fillable ) ) { |
|
455 | 12 | return true; |
|
456 | } |
||
457 | |||
458 | // If it's explicitly guarded, then it's not fillable. |
||
459 | 9 | if ( in_array( $name, $this->guarded ) ) { |
|
460 | 9 | return false; |
|
461 | } |
||
462 | |||
463 | // If fillable hasn't been defined, then everything else fillable. |
||
464 | return ! $this->fillable; |
||
465 | } |
||
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 ) { |
|
477 | 27 | if ( is_object( $value ) ) { |
|
478 | 27 | $this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $value ); |
|
479 | 18 | } else { |
|
480 | $this->attributes[ self::OBJECT_KEY ] = null; |
||
481 | |||
482 | if ( $this->uses_wp_object() ) { |
||
483 | $this->create_wp_object(); |
||
484 | } |
||
485 | } |
||
486 | |||
487 | 27 | return $this; |
|
488 | } |
||
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 ) { |
|
498 | 6 | $this->attributes[ self::TABLE_KEY ] = $value; |
|
499 | |||
500 | 6 | return $this; |
|
501 | } |
||
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() { |
|
512 | 40 | switch ( true ) { |
|
513 | 60 | case $this instanceof UsesWordPressPost: |
|
514 | 60 | $object = new WP_Post( (object) array() ); |
|
515 | 60 | break; |
|
516 | case $this instanceof UsesWordPressTerm: |
||
517 | $object = new WP_Term( (object) array() ); |
||
518 | break; |
||
519 | default: |
||
520 | throw new LogicException; |
||
521 | break; |
||
522 | } |
||
523 | |||
524 | 60 | $this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $object ); |
|
525 | 60 | } |
|
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 ) { |
|
539 | 60 | if ( $this instanceof UsesWordPressPost ) { |
|
540 | 60 | $object->post_type = static::get_post_type(); |
|
541 | 40 | } |
|
542 | |||
543 | 60 | if ( $this instanceof UsesWordPressTerm ) { |
|
544 | $object->taxonomy = static::get_taxonomy(); |
||
545 | } |
||
546 | |||
547 | 60 | return $object; |
|
548 | } |
||
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 ) { |
|
560 | 24 | return $this->get_attribute( $name ); |
|
561 | } |
||
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 ) { |
|
573 | 51 | if ( $method = $this->has_map_method( $name ) ) { |
|
574 | 24 | return $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()}; |
|
575 | } |
||
576 | |||
577 | 39 | if ( $method = $this->has_compute_method( $name ) ) { |
|
578 | 12 | return $this->{$method}(); |
|
579 | } |
||
580 | |||
581 | 36 | if ( isset( $this->attributes[ self::TABLE_KEY ][ $name ] ) ) { |
|
582 | 33 | return $this->attributes[ self::TABLE_KEY ][ $name ]; |
|
583 | } |
||
584 | |||
585 | 3 | if ( isset( $this->defaults[ $name ] ) ) { |
|
586 | return $this->defaults[ $name ]; |
||
587 | } |
||
588 | |||
589 | 3 | return null; |
|
590 | } |
||
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 ) { |
|
602 | 6 | $original_attributes = $this->original; |
|
603 | |||
604 | 6 | if ( ! is_object( $original_attributes[ static::OBJECT_KEY ] ) ) { |
|
605 | unset( $original_attributes[ static::OBJECT_KEY ] ); |
||
606 | } |
||
607 | |||
608 | 6 | $original = new static( $original_attributes ); |
|
609 | |||
610 | 6 | return $original->get_attribute( $name ); |
|
611 | } |
||
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() { |
||
622 | if ( $this instanceof UsesWordPressPost ) { |
||
623 | return $this->get_underlying_wp_object()->ID; |
||
624 | } |
||
625 | |||
626 | if ( $this instanceof UsesWordPressTerm ) { |
||
627 | return $this->get_underlying_wp_object()->term_id; |
||
628 | } |
||
629 | |||
630 | if ( $this instanceof UsesCustomTable ) { |
||
631 | return $this->get_attribute( $this->get_primary_key() ); |
||
632 | } |
||
633 | |||
634 | // Model w/o wp_object not yet supported. |
||
635 | throw new LogicException; |
||
636 | } |
||
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 ) { |
|
650 | 96 | if ( method_exists( $this, $method = "map_{$name}" ) ) { |
|
651 | 60 | return $method; |
|
652 | } |
||
653 | |||
654 | 96 | return false; |
|
655 | } |
||
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 ) { |
|
668 | 51 | if ( method_exists( $this, $method = "compute_{$name}" ) ) { |
|
669 | 21 | return $method; |
|
670 | } |
||
671 | |||
672 | 48 | return false; |
|
673 | } |
||
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 | * @return $this |
||
682 | */ |
||
683 | 96 | public function clear() { |
|
684 | 96 | $keys = array_merge( |
|
685 | 96 | $this->get_table_keys(), |
|
686 | 96 | $this->get_wp_object_keys() |
|
687 | 64 | ); |
|
688 | |||
689 | 96 | foreach ( $keys as $key ) { |
|
690 | try { |
||
691 | 96 | $this->set_attribute( $key, null ); |
|
692 | 96 | } catch ( $e ) { |
|
|
|||
693 | // We won't clear out guarded attributes. |
||
694 | if ( ! ( $e instanceof GuardedPropertyException ) ) { |
||
695 | 64 | throw $e; |
|
696 | } |
||
697 | 96 | } |
|
698 | } |
||
699 | |||
700 | return $this; |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * Unguards the model. |
||
705 | * |
||
706 | 96 | * Sets the model to be unguarded, allowing the filling of |
|
707 | 96 | * guarded attributes. |
|
708 | 96 | */ |
|
709 | public function unguard() { |
||
710 | $this->is_guarded = false; |
||
711 | } |
||
712 | |||
713 | /** |
||
714 | * Reguards the model. |
||
715 | * |
||
716 | 96 | * Sets the model to be guarded, preventing filling of |
|
717 | 96 | * guarded attributes. |
|
718 | 96 | */ |
|
719 | public function reguard() { |
||
720 | $this->is_guarded = true; |
||
721 | } |
||
722 | |||
723 | /** |
||
724 | * Retrieves all the compute methods on the model. |
||
725 | 9 | * |
|
726 | 9 | * @return array |
|
727 | 3 | */ |
|
728 | 9 | protected function get_compute_methods() { |
|
729 | 9 | $methods = get_class_methods( get_called_class() ); |
|
730 | 9 | $methods = array_filter( $methods, function ( $method ) { |
|
731 | 6 | return strrpos( $method, 'compute_', - strlen( $method ) ) !== false; |
|
732 | 9 | } ); |
|
733 | $methods = array_map( function ( $method ) { |
||
734 | 9 | return substr( $method, strlen( 'compute_' ) ); |
|
735 | }, $methods ); |
||
736 | |||
737 | return $methods; |
||
738 | } |
||
739 | |||
740 | 96 | /** |
|
741 | 96 | * Sets up the memo array for the creating model. |
|
742 | 9 | */ |
|
743 | 6 | private function maybe_boot() { |
|
744 | 96 | if ( ! isset( self::$memo[ get_called_class() ] ) ) { |
|
745 | self::$memo[ get_called_class() ] = array(); |
||
746 | } |
||
747 | } |
||
748 | |||
749 | /** |
||
750 | * Whether this Model uses an underlying WordPress object. |
||
751 | 96 | * |
|
752 | 96 | * @return bool |
|
753 | 96 | */ |
|
754 | protected function uses_wp_object() { |
||
755 | return $this instanceof UsesWordPressPost || |
||
756 | $this instanceof UsesWordPressTerm; |
||
759 |