Completed
Push — master ( 122574...f8fd12 )
by James
03:56
created

Model::serialize()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8

Importance

Changes 5
Bugs 0 Features 2
Metric Value
c 5
b 0
f 2
dl 0
loc 30
ccs 20
cts 20
cp 1
rs 5.3846
cc 8
eloc 17
nc 6
nop 0
crap 8
1
<?php
2
namespace Intraxia\Jaxion\Axolotl;
3
4
use Intraxia\Jaxion\Contract\Axolotl\Serializes;
5
use Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost;
6
use Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm;
7
use LogicException;
8
use WP_Post;
9
use WP_Term;
10
11
/**
12
 * Class Model
13
 *
14
 * Shared model methods and properties, allowing models
15
 * to transparently map some attributes to an underlying WP_Post
16
 * object and others to postmeta or a custom table.
17
 *
18
 * @package    Intraxia\Jaxion
19
 * @subpackage Axolotl
20
 * @since      0.1.0
21
 */
22
abstract class Model implements Serializes {
23
	/**
24
	 * Table attribute key.
25
	 */
26
	const TABLE_KEY = '@@table';
27
28
	/**
29
	 * Object attribute key.
30
	 */
31
	const OBJECT_KEY = '@@object';
32
33
	/**
34
	 * Memoized values for class methods.
35
	 *
36
	 * @var array
37
	 */
38
	private static $memo = array();
39
40
	/**
41
	 * Model attributes.
42
	 *
43
	 * @var array
44
	 */
45
	private $attributes = array(
46
		self::TABLE_KEY  => array(),
47
		self::OBJECT_KEY => null,
48
	);
49
50
	/**
51
	 * Model's original attributes.
52
	 *
53
	 * @var array
54
	 */
55
	private $original = array(
56
		self::TABLE_KEY  => array(),
57
		self::OBJECT_KEY => null,
58
	);
59
60
	/**
61
	 * Properties which are allowed to be set on the model.
62
	 *
63
	 * If this array is empty, any attributes can be set on the model.
64
	 *
65
	 * @var string[]
66
	 */
67
	protected $fillable = array();
68
69
	/**
70
	 * Properties which cannot be automatically filled on the model.
71
	 *
72
	 * If the model is unguarded, these properties can be filled.
73
	 *
74
	 * @var array
75
	 */
76
	protected $guarded = array();
77
78
	/**
79
	 * Properties which should not be serialized.
80
	 *
81
	 * @var array
82
	 */
83
	protected $hidden = array();
84
85
	/**
86
	 * Properties which should be serialized.
87
	 *
88
	 * @var array
89
	 */
90
	protected $visible = array();
91
92
	/**
93
	 * Whether the model's properties are guarded.
94
	 *
95
	 * When false, allows guarded properties to be filled.
96
	 *
97
	 * @var bool
98
	 */
99
	protected $is_guarded = true;
100
101
	/**
102
	 * Constructs a new model with provided attributes.
103
	 *
104
	 * If self::OBJECT_KEY is passed as one of the attributes, the underlying post
105
	 * will be overwritten.
106
	 *
107
	 * @param array <string, mixed> $attributes
108
	 */
109 69
	public function __construct( array $attributes = array() ) {
110 69
		$this->maybe_boot();
111 69
		$this->sync_original();
112
113 69
		if ( $this->uses_wp_object() ) {
114 66
			$this->create_wp_object();
115 66
		}
116
117 69
		$this->unguard();
118 69
		$this->refresh( $attributes );
119 69
		$this->reguard();
120 69
	}
121
122
	/**
123
	 * Refreshes the model's current attributes with the provided array.
124
	 *
125
	 * The model's attributes will match what was provided in the array,
126
	 * and any attributes not passed
127
	 *
128
	 * @param array $attributes
129
	 *
130
	 * @return $this
131
	 */
132 69
	public function refresh( array $attributes ) {
133 69
		$this->clear();
134
135 69
		return $this->merge( $attributes );
136
	}
137
138
	/**
139
	 * Merges the provided attributes with the provided array.
140
	 *
141
	 * @param array $attributes
142
	 *
143
	 * @return $this
144
	 */
145 69
	public function merge( array $attributes ) {
146 69
		foreach ( $attributes as $name => $value ) {
147 39
			$this->set_attribute( $name, $value );
148 69
		}
149
150 69
		return $this;
151
	}
152
153
	/**
154
	 * Get the model's table attributes.
155
	 *
156
	 * Returns the array of for the model that will either need to be
157
	 * saved in postmeta or a separate table.
158
	 *
159
	 * @return array
160
	 */
161 12
	public function get_table_attributes() {
162 12
		return $this->attributes[ self::TABLE_KEY ];
163
	}
164
165
	/**
166
	 * Get the model's original attributes.
167
	 *
168
	 * @return array
169
	 */
170 6
	public function get_original_table_attributes() {
171 6
		return $this->original[ self::TABLE_KEY ];
172
	}
173
174
	/**
175
	 * Retrieve an array of the attributes on the model
176
	 * that have changed compared to the model's
177
	 * original data.
178
	 *
179
	 * @return array
180
	 */
181 3
	public function get_changed_table_attributes() {
182 3
		$changed = array();
183
184 3
		foreach ( $this->get_table_attributes() as $key => $value ) {
185
			if ( $value !==
186 3
			     $this->get_original_attribute( $key )
187 3
			) {
188 3
				$changed[ $key ] = $value;
189 3
			}
190 3
		}
191
192 3
		return $changed;
193
	}
194
195
	/**
196
	 * Get the model's underlying post.
197
	 *
198
	 * Returns the underlying WP_Post object for the model, representing
199
	 * the data that will be save in the wp_posts table.
200
	 *
201
	 * @return false|WP_Post|WP_Term
202
	 */
203 18
	public function get_underlying_wp_object() {
204 18
		if ( isset( $this->attributes[ self::OBJECT_KEY ] ) ) {
205 15
			return $this->attributes[ self::OBJECT_KEY ];
206
		}
207
208 3
		return false;
209
	}
210
211
	/**
212
	 * Get the model's original underlying post.
213
	 *
214
	 * @return WP_Post
215
	 */
216 6
	public function get_original_underlying_wp_object() {
217 6
		return $this->original[ self::OBJECT_KEY ];
218
	}
219
220
	/**
221
	 * Get the model attributes on the WordPress object
222
	 * that have changed compared to the model's
223
	 * original attributes.
224
	 *
225
	 * @return array
226
	 */
227 3
	public function get_changed_wp_object_attributes() {
228 3
		$changed = array();
229
230 3
		foreach ( $this->get_wp_object_keys() as $key ) {
231 3
			if ( $this->get_attribute( $key ) !==
232 3
			     $this->get_original_attribute( $key )
233 3
			) {
234 3
				$changed[ $key ] = $this->get_attribute( $key );
235 3
			}
236 3
		}
237
238 3
		return $changed;
239
	}
240
241
	/**
242
	 * Magic __set method.
243
	 *
244
	 * Passes the name and value to set_attribute, which is where the magic happens.
245
	 *
246
	 * @param string $name
247
	 * @param mixed  $value
248
	 */
249 6
	public function __set( $name, $value ) {
250 6
		$this->set_attribute( $name, $value );
251 6
	}
252
253
	/**
254
	 * Sets the model attributes.
255
	 *
256
	 * Checks whether the model attribute can be set, check if it
257
	 * maps to the WP_Post property, otherwise, assigns it to the
258
	 * table attribute array.
259
	 *
260
	 * @param string $name
261
	 * @param mixed  $value
262
	 *
263
	 * @return $this
264
	 *
265
	 * @throws GuardedPropertyException
266
	 */
267 69
	public function set_attribute( $name, $value ) {
268 69
		if ( self::OBJECT_KEY === $name ) {
269 27
			return $this->override_wp_object( $value );
270
		}
271
272 69
		if ( self::TABLE_KEY === $name ) {
273 6
			return $this->override_table( $value );
274
		}
275
276 69
		if ( ! $this->is_fillable( $name ) ) {
277 9
			throw new GuardedPropertyException;
278
		}
279
280 69
		if ( $method = $this->has_map_method( $name ) ) {
281 66
			$this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()} = $value;
282 66
		} else {
283 69
			$this->attributes[ self::TABLE_KEY ][ $name ] = $value;
284
		}
285
286 69
		return $this;
287
	}
288
289
	/**
290
	 * Retrieves all the attribute keys for the model.
291
	 *
292
	 * @return array
293
	 */
294 18
	public function get_attribute_keys() {
295 18
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
296 18
			return self::$memo[ get_called_class() ][ __METHOD__ ];
297
		}
298
299 9
		return self::$memo[ get_called_class() ][ __METHOD__ ]
300 9
			= array_merge(
301 9
				$this->fillable,
302 9
				$this->guarded,
303 9
				$this->get_compute_methods()
304 9
			);
305
	}
306
307
	/**
308
	 * Retrieves the attribute keys that aren't mapped to a post.
309
	 *
310
	 * @return array
311
	 */
312 69 View Code Duplication
	public function get_table_keys() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
313 69
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
314 66
			return self::$memo[ get_called_class() ][ __METHOD__ ];
315
		}
316
317 9
		$keys = array();
318
319 9
		foreach ( $this->get_attribute_keys() as $key ) {
320 9
			if ( ! $this->has_map_method( $key ) &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_map_method($key) of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
321 9
			     ! $this->has_compute_method( $key )
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_compute_method($key) of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
322 9
			) {
323 9
				$keys[] = $key;
324 9
			}
325 9
		}
326
327 9
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
328
	}
329
330
	/**
331
	 * Retrieves the attribute keys that are mapped to a post.
332
	 *
333
	 * @return array
334
	 */
335 69 View Code Duplication
	public function get_wp_object_keys() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
336 69
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
337 66
			return self::$memo[ get_called_class() ][ __METHOD__ ];
338
		}
339
340 9
		$keys = array();
341
342 9
		foreach ( $this->get_attribute_keys() as $key ) {
343 9
			if ( $this->has_map_method( $key ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_map_method($key) of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
344 6
				$keys[] = $key;
345 6
			}
346 9
		}
347
348 9
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
349
	}
350
351
	/**
352
	 * Returns the model's keys that are computed at call time.
353
	 *
354
	 * @return array
355
	 */
356 3 View Code Duplication
	public function get_computed_keys() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
357 3
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
358 3
			return self::$memo[ get_called_class() ][ __METHOD__ ];
359
		}
360
361 3
		$keys = array();
362
363 3
		foreach ( $this->get_attribute_keys() as $key ) {
364 3
			if ( $this->has_compute_method( $key ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->has_compute_method($key) of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
365 3
				$keys[] = $key;
366 3
			}
367 3
		}
368
369 3
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
370
	}
371
372
	/**
373
	 * Serializes the model's public data into an array.
374
	 *
375
	 * @return array
376
	 */
377 12
	public function serialize() {
378 12
		$attributes = array();
379
380 12
		if ( $this->visible ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->visible of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
381
			// If visible attributes are set, we'll only reveal those.
382 6
			foreach ( $this->visible as $key ) {
383 6
				$attributes[ $key ] = $this->get_attribute( $key );
384 6
			}
385 12
		} elseif ( $this->hidden ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->hidden of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
386
			// If hidden attributes are set, we'll grab everything and hide those.
387 3
			foreach ( $this->get_attribute_keys() as $key ) {
388 3
				if ( ! in_array( $key, $this->hidden ) ) {
389 3
					$attributes[ $key ] = $this->get_attribute( $key );
390 3
				}
391 3
			}
392 3
		} else {
393
			// If nothing is hidden/visible, we'll grab and reveal everything.
394 3
			foreach ( $this->get_attribute_keys() as $key ) {
395 3
				$attributes[ $key ] = $this->get_attribute( $key );
396 3
			}
397
		}
398
399
		return array_map( function ( $attribute ) {
400 12
			if ( $attribute instanceof Serializes ) {
401 3
				return $attribute->serialize();
402
			}
403
404 12
			return $attribute;
405 12
		}, $attributes );
406
	}
407
408
	/**
409
	 * Syncs the current attributes to the model's original.
410
	 *
411
	 * @return $this
412
	 */
413 69
	public function sync_original() {
414 69
		$this->original = $this->attributes;
415
416 69
		if ( $this->attributes[ self::OBJECT_KEY ] ) {
417 9
			$this->original[ self::OBJECT_KEY ] = clone $this->attributes[ self::OBJECT_KEY ];
418 9
		}
419
420 69
		return $this;
421
	}
422
423
	/**
424
	 * Checks if a given attribute is mass-fillable.
425
	 *
426
	 * Returns true if the attribute can be filled, false if it can't.
427
	 *
428
	 * @param string $name
429
	 *
430
	 * @return bool
431
	 */
432 69
	private function is_fillable( $name ) {
433
		// If this model isn't guarded, everything is fillable.
434 69
		if ( ! $this->is_guarded ) {
435 69
			return true;
436
		}
437
438
		// If it's in the fillable array, then it's fillable.
439 18
		if ( in_array( $name, $this->fillable ) ) {
440 12
			return true;
441
		}
442
443
		// If it's explicitly guarded, then it's not fillable.
444 9
		if ( in_array( $name, $this->guarded ) ) {
445 9
			return false;
446
		}
447
448
		// If fillable hasn't been defined, then everything else fillable.
449
		return ! $this->fillable;
450
	}
451
452
	/**
453
	 * Overrides the current WordPress object with a provided one.
454
	 *
455
	 * Resets the post's default values and stores it in the attributes.
456
	 *
457
	 * @param WP_Post|WP_Term $value
458
	 *
459
	 * @return $this
460
	 */
461 27
	private function override_wp_object( $value ) {
462 27
		$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $value );
463
464 27
		return $this;
465
	}
466
467
	/**
468
	 * Overrides the current table attributes array with a provided one.
469
	 *
470
	 * @param array $value
471
	 *
472
	 * @return $this
473
	 */
474 6
	private function override_table( array $value ) {
475 6
		$this->attributes[ self::TABLE_KEY ] = $value;
476
477 6
		return $this;
478
	}
479
480
	/**
481
	 * Create and set with a new blank post.
482
	 *
483
	 * Creates a new WP_Post object, assigns it the default attributes,
484
	 * and stores it in the attributes.
485
	 *
486
	 * @throws LogicException
487
	 */
488 66
	private function create_wp_object() {
489 66
		switch ( true ) {
490 66
			case $this instanceof UsesWordPressPost:
491 66
				$object = new WP_Post( (object) array() );
492 66
				break;
493
			case $this instanceof UsesWordPressTerm:
494
				$object = new WP_Term( (object) array() );
495
				break;
496
			default:
497
				throw new LogicException;
498
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
499
		}
500
501 66
		$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $object );
502 66
	}
503
504
	/**
505
	 * Enforces values on the post that can't change.
506
	 *
507
	 * Primarily, this is used to make sure the post_type always maps
508
	 * to the model's "$type" property, but this can all be overridden
509
	 * by the developer to enforce other values in the model.
510
	 *
511
	 * @param object $object
512
	 *
513
	 * @return object
514
	 */
515 66
	protected function set_wp_object_constants( $object ) {
516 66
		if ( $this instanceof UsesWordPressPost ) {
517 66
			$object->post_type = $this::get_post_type();
518 66
		}
519
520 66
		if ( $this instanceof UsesWordPressTerm ) {
521
			$object->taxonomy = $this::get_taxonomy();
522
		}
523
524 66
		return $object;
525
	}
526
527
	/**
528
	 * Magic __get method.
529
	 *
530
	 * Passes the name and value to get_attribute, which is where the magic happens.
531
	 *
532
	 * @param string $name
533
	 *
534
	 * @return mixed
535
	 */
536 18
	public function __get( $name ) {
537 18
		return $this->get_attribute( $name );
538
	}
539
540
	/**
541
	 * Retrieves the model attribute.
542
	 *
543
	 * @param string $name
544
	 *
545
	 * @return mixed
546
	 *
547
	 * @throws PropertyDoesNotExistException If property isn't found.
548
	 */
549 45
	public function get_attribute( $name ) {
550 45
		if ( $method = $this->has_map_method( $name ) ) {
551 33
			$value = $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()};
552 45
		} elseif ( $method = $this->has_compute_method( $name ) ) {
553 15
			$value = $this->{$method}();
554 15
		} else {
555 24
			if ( ! isset( $this->attributes[ self::TABLE_KEY ][ $name ] ) ) {
556 3
				throw new PropertyDoesNotExistException( $name );
557
			}
558
559 21
			$value = $this->attributes[ self::TABLE_KEY ][ $name ];
560
		}
561
562 42
		return $value;
563
	}
564
565
	/**
566
	 * Retrieve the model's original attribute value.
567
	 *
568
	 * @param string $name
569
	 *
570
	 * @return mixed
571
	 *
572
	 * @throws PropertyDoesNotExistException If property isn't found.
573
	 */
574 6
	public function get_original_attribute( $name ) {
575 6
		$original = new static( $this->original );
576
577 6
		return $original->get_attribute( $name );
578
	}
579
580
	/**
581
	 * Fetches the Model's primary ID, depending on the model
582
	 * implementation.
583
	 *
584
	 * @return int
585
	 *
586
	 * @throws LogicException
587
	 */
588
	public function get_primary_id() {
589
		if ( $this instanceof UsesWordPressPost ) {
590
			return $this->get_underlying_wp_object()->ID;
591
		}
592
593
		if ( $this instanceof UsesWordPressTerm ) {
594
			return $this->get_underlying_wp_object()->term_id;
595
		}
596
597
		// Model w/o wp_object not yet supported.
598
		throw new LogicException;
599
	}
600
601
	/**
602
	 * Checks whether the attribute has a map method.
603
	 *
604
	 * This is used to determine whether the attribute maps to a
605
	 * property on the underlying WP_Post object. Returns the
606
	 * method if one exists, returns false if it doesn't.
607
	 *
608
	 * @param string $name
609
	 *
610
	 * @return false|string
611
	 */
612 69
	protected function has_map_method( $name ) {
613 69
		if ( method_exists( $this, $method = "map_{$name}" ) ) {
614 66
			return $method;
615
		}
616
617 69
		return false;
618
	}
619
620
	/**
621
	 * Checks whether the attribute has a compute method.
622
	 *
623
	 * This is used to determine if the attribute should be computed
624
	 * from other attributes.
625
	 *
626
	 * @param string $name
627
	 *
628
	 * @return false|string
629
	 */
630 39
	protected function has_compute_method( $name ) {
631 39
		if ( method_exists( $this, $method = "compute_{$name}" ) ) {
632 24
			return $method;
633
		}
634
635 36
		return false;
636
	}
637
638
	/**
639
	 * Clears all the current attributes from the model.
640
	 *
641
	 * This does not touch the model's original attributes, and will
642
	 * only clear fillable attributes, unless the model is unguarded.
643
	 *
644
	 * @return $this
645
	 */
646 69
	public function clear() {
647 69
		$keys = array_merge(
648 69
			$this->get_table_keys(),
649 69
			$this->get_wp_object_keys()
650 69
		);
651
652 69
		foreach ( $keys as $key ) {
653
			try {
654 69
				$this->set_attribute( $key, null );
655 69
			} catch ( GuardedPropertyException $e ) {
656
				// We won't clear out guarded attributes.
657
			}
658 69
		}
659
660 69
		return $this;
661
	}
662
663
	/**
664
	 * Unguards the model.
665
	 *
666
	 * Sets the model to be unguarded, allowing the filling of
667
	 * guarded attributes.
668
	 */
669 69
	public function unguard() {
670 69
		$this->is_guarded = false;
671 69
	}
672
673
	/**
674
	 * Reguards the model.
675
	 *
676
	 * Sets the model to be guarded, preventing filling of
677
	 * guarded attributes.
678
	 */
679 69
	public function reguard() {
680 69
		$this->is_guarded = true;
681 69
	}
682
683
	/**
684
	 * Retrieves all the compute methods on the model.
685
	 *
686
	 * @return array
687
	 */
688 9
	protected function get_compute_methods() {
689 9
		$methods = get_class_methods( get_called_class() );
690
		$methods = array_filter( $methods, function ( $method ) {
691 9
			return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
692 9
		} );
693 9
		$methods = array_map( function ( $method ) {
694 6
			return substr( $method, strlen( 'compute_' ) );
695 9
		}, $methods );
696
697 9
		return $methods;
698
	}
699
700
	/**
701
	 * Sets up the memo array for the creating model.
702
	 */
703 69
	private function maybe_boot() {
704 69
		if ( ! isset( self::$memo[ get_called_class() ] ) ) {
705 9
			self::$memo[ get_called_class() ] = array();
706 9
		}
707 69
	}
708
709
	/**
710
	 * Whether this Model uses an underlying WordPress object.
711
	 *
712
	 * @return bool
713
	 */
714 69
	protected function uses_wp_object() {
715 69
		return $this instanceof UsesWordPressPost ||
716 69
			$this instanceof UsesWordPressTerm;
717
	}
718
}
719