Completed
Push — master ( 54e09a...250b3d )
by James
11s
created

Model::get_original_attribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 7
cp 0.7143
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
crap 2.0932
1
<?php
2
namespace Intraxia\Jaxion\Axolotl;
3
4
use Exception;
5
use Intraxia\Jaxion\Contract\Axolotl\Serializes;
6
use Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost;
7
use Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm;
8
use LogicException;
9
use WP_Post;
10
use WP_Term;
11
12
/**
13
 * Class Model
14
 *
15
 * Shared model methods and properties, allowing models
16
 * to transparently map some attributes to an underlying WP_Post
17
 * object and others to postmeta or a custom table.
18
 *
19
 * @package    Intraxia\Jaxion
20
 * @subpackage Axolotl
21
 * @since      0.1.0
22
 */
23
abstract class Model implements Serializes {
24
	/**
25
	 * Table attribute key.
26
	 */
27
	const TABLE_KEY = '@@table';
28
29
	/**
30
	 * Object attribute key.
31
	 */
32
	const OBJECT_KEY = '@@object';
33
34
	/**
35
	 * Memoized values for class methods.
36
	 *
37
	 * @var array
38
	 */
39
	private static $memo = array();
40
41
	/**
42
	 * Model attributes.
43
	 *
44
	 * @var array
45
	 */
46
	private $attributes = array(
47
		self::TABLE_KEY  => array(),
48
		self::OBJECT_KEY => null,
49
	);
50
51
	/**
52
	 * Model's original attributes.
53
	 *
54
	 * @var array
55
	 */
56
	private $original = array(
57
		self::TABLE_KEY  => array(),
58
		self::OBJECT_KEY => null,
59
	);
60
61
	/**
62
	 * Default attribute values.
63
	 *
64
	 * @var array
65
	 */
66
	protected $defaults = array();
67
68
	/**
69
	 * Properties which are allowed to be set on the model.
70
	 *
71
	 * If this array is empty, any attributes can be set on the model.
72
	 *
73
	 * @var string[]
74
	 */
75
	protected $fillable = array();
76
77
	/**
78
	 * Properties which cannot be automatically filled on the model.
79
	 *
80
	 * If the model is unguarded, these properties can be filled.
81
	 *
82
	 * @var array
83
	 */
84
	protected $guarded = array();
85
86
	/**
87
	 * Properties which should not be serialized.
88
	 *
89
	 * @var array
90
	 */
91
	protected $hidden = array();
92
93
	/**
94
	 * Properties which should be serialized.
95
	 *
96
	 * @var array
97
	 */
98
	protected $visible = array();
99
100
	/**
101
	 * Whether the model's properties are guarded.
102
	 *
103
	 * When false, allows guarded properties to be filled.
104
	 *
105
	 * @var bool
106
	 */
107
	protected $is_guarded = true;
108
109
	/**
110
	 * Constructs a new model with provided attributes.
111
	 *
112
	 * If self::OBJECT_KEY is passed as one of the attributes, the underlying post
113
	 * will be overwritten.
114
	 *
115
	 * @param array <string, mixed> $attributes
116
	 */
117 96
	public function __construct( array $attributes = array() ) {
118 96
		$this->maybe_boot();
119 96
		$this->sync_original();
120
121 96
		if ( $this->uses_wp_object() ) {
122 60
			$this->create_wp_object();
123 60
		}
124
125 96
		$this->unguard();
126 96
		$this->refresh( $attributes );
127 96
		$this->reguard();
128 96
	}
129
130
	/**
131
	 * Refreshes the model's current attributes with the provided array.
132
	 *
133
	 * The model's attributes will match what was provided in the array,
134
	 * and any attributes not passed
135
	 *
136
	 * @param array $attributes
137
	 *
138
	 * @return $this
139
	 */
140 96
	public function refresh( array $attributes ) {
141 96
		$this->clear();
142
143 96
		return $this->merge( $attributes );
144
	}
145
146
	/**
147
	 * Merges the provided attributes with the provided array.
148
	 *
149
	 * @param array $attributes
150
	 *
151
	 * @return $this
152
	 */
153 96
	public function merge( array $attributes ) {
154 96
		foreach ( $attributes as $name => $value ) {
155 45
			$this->set_attribute( $name, $value );
156 96
		}
157
158 96
		return $this;
159
	}
160
161
	/**
162
	 * Get the model's table attributes.
163
	 *
164
	 * Returns the array of for the model that will either need to be
165
	 * saved in postmeta or a separate table.
166
	 *
167
	 * @return array
168
	 */
169 12
	public function get_table_attributes() {
170 12
		return $this->attributes[ self::TABLE_KEY ];
171
	}
172
173
	/**
174
	 * Get the model's original attributes.
175
	 *
176
	 * @return array
177
	 */
178 6
	public function get_original_table_attributes() {
179 6
		return $this->original[ self::TABLE_KEY ];
180
	}
181
182
	/**
183
	 * Retrieve an array of the attributes on the model
184
	 * that have changed compared to the model's
185
	 * original data.
186
	 *
187
	 * @return array
188
	 */
189 3
	public function get_changed_table_attributes() {
190 3
		$changed = array();
191
192 3
		foreach ( $this->get_table_attributes() as $key => $value ) {
193
			if ( $value !==
194 3
				 $this->get_original_attribute( $key )
195 3
			) {
196 3
				$changed[ $key ] = $value;
197 3
			}
198 3
		}
199
200 3
		return $changed;
201
	}
202
203
	/**
204
	 * Get the model's underlying post.
205
	 *
206
	 * Returns the underlying WP_Post object for the model, representing
207
	 * the data that will be save in the wp_posts table.
208
	 *
209
	 * @return false|WP_Post|WP_Term
210
	 */
211 18
	public function get_underlying_wp_object() {
212 18
		if ( isset( $this->attributes[ self::OBJECT_KEY ] ) ) {
213 15
			return $this->attributes[ self::OBJECT_KEY ];
214
		}
215
216 3
		return false;
217
	}
218
219
	/**
220
	 * Get the model's original underlying post.
221
	 *
222
	 * @return WP_Post
223
	 */
224 6
	public function get_original_underlying_wp_object() {
225 6
		return $this->original[ self::OBJECT_KEY ];
226
	}
227
228
	/**
229
	 * Get the model attributes on the WordPress object
230
	 * that have changed compared to the model's
231
	 * original attributes.
232
	 *
233
	 * @return array
234
	 */
235 3
	public function get_changed_wp_object_attributes() {
236 3
		$changed = array();
237
238 3
		foreach ( $this->get_wp_object_keys() as $key ) {
239 3
			if ( $this->get_attribute( $key ) !==
240 3
				 $this->get_original_attribute( $key )
241 3
			) {
242 3
				$changed[ $key ] = $this->get_attribute( $key );
243 3
			}
244 3
		}
245
246 3
		return $changed;
247
	}
248
249
	/**
250
	 * Magic __set method.
251
	 *
252
	 * Passes the name and value to set_attribute, which is where the magic happens.
253
	 *
254
	 * @param string $name
255
	 * @param mixed  $value
256
	 */
257 6
	public function __set( $name, $value ) {
258 6
		$this->set_attribute( $name, $value );
259 6
	}
260
261
	/**
262
	 * Sets the model attributes.
263
	 *
264
	 * Checks whether the model attribute can be set, check if it
265
	 * maps to the WP_Post property, otherwise, assigns it to the
266
	 * table attribute array.
267
	 *
268
	 * @param string $name
269
	 * @param mixed  $value
270
	 *
271
	 * @return $this
272
	 *
273
	 * @throws GuardedPropertyException
274
	 */
275 96
	public function set_attribute( $name, $value ) {
276 96
		if ( self::OBJECT_KEY === $name ) {
277 27
			return $this->override_wp_object( $value );
278
		}
279
280 96
		if ( self::TABLE_KEY === $name ) {
281 6
			return $this->override_table( $value );
282
		}
283
284 96
		if ( ! $this->is_fillable( $name ) ) {
285 9
			throw new GuardedPropertyException;
286
		}
287
288 96
		if ( $method = $this->has_map_method( $name ) ) {
289 60
			$this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()} = $value;
290 60
		} else {
291 96
			$this->attributes[ self::TABLE_KEY ][ $name ] = $value;
292
		}
293
294 96
		return $this;
295
	}
296
297
	/**
298
	 * Retrieves all the attribute keys for the model.
299
	 *
300
	 * @return array
301
	 */
302 18
	public function get_attribute_keys() {
303 18
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
304 18
			return self::$memo[ get_called_class() ][ __METHOD__ ];
305
		}
306
307 9
		return self::$memo[ get_called_class() ][ __METHOD__ ]
308 9
			= array_merge(
309 9
				$this->fillable,
310 9
				$this->guarded,
311 9
				$this->get_compute_methods()
312 9
			);
313
	}
314
315
	/**
316
	 * Retrieves the attribute keys that aren't mapped to a post.
317
	 *
318
	 * @return array
319
	 */
320 96 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...
321 96
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
322 93
			return self::$memo[ get_called_class() ][ __METHOD__ ];
323
		}
324
325 9
		$keys = array();
326
327 9
		foreach ( $this->get_attribute_keys() as $key ) {
328 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...
329 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...
330 9
			) {
331 9
				$keys[] = $key;
332 9
			}
333 9
		}
334
335 9
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
336
	}
337
338
	/**
339
	 * Retrieves the attribute keys that are mapped to a post.
340
	 *
341
	 * @return array
342
	 */
343 96 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...
344 96
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
345 93
			return self::$memo[ get_called_class() ][ __METHOD__ ];
346
		}
347
348 9
		$keys = array();
349
350 9
		foreach ( $this->get_attribute_keys() as $key ) {
351 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...
352 6
				$keys[] = $key;
353 6
			}
354 9
		}
355
356 9
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
357
	}
358
359
	/**
360
	 * Returns the model's keys that are computed at call time.
361
	 *
362
	 * @return array
363
	 */
364 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...
365 3
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
366 3
			return self::$memo[ get_called_class() ][ __METHOD__ ];
367
		}
368
369 3
		$keys = array();
370
371 3
		foreach ( $this->get_attribute_keys() as $key ) {
372 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...
373 3
				$keys[] = $key;
374 3
			}
375 3
		}
376
377 3
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
378
	}
379
380
	/**
381
	 * Serializes the model's public data into an array.
382
	 *
383
	 * @return array
384
	 */
385 12
	public function serialize() {
386 12
		$attributes = array();
387
388 12
		if ( $this->visible ) {
389
			// If visible attributes are set, we'll only reveal those.
390 6
			foreach ( $this->visible as $key ) {
391 6
				$attributes[ $key ] = $this->get_attribute( $key );
392 6
			}
393 12
		} elseif ( $this->hidden ) {
394
			// If hidden attributes are set, we'll grab everything and hide those.
395 3
			foreach ( $this->get_attribute_keys() as $key ) {
396 3
				if ( ! in_array( $key, $this->hidden ) ) {
397 3
					$attributes[ $key ] = $this->get_attribute( $key );
398 3
				}
399 3
			}
400 3
		} else {
401
			// If nothing is hidden/visible, we'll grab and reveal everything.
402 3
			foreach ( $this->get_attribute_keys() as $key ) {
403 3
				$attributes[ $key ] = $this->get_attribute( $key );
404 3
			}
405
		}
406
407
		return array_map( function ( $attribute ) {
408 12
			if ( $attribute instanceof Serializes ) {
409 3
				return $attribute->serialize();
410
			}
411
412 12
			return $attribute;
413 12
		}, $attributes );
414
	}
415
416
	/**
417
	 * Syncs the current attributes to the model's original.
418
	 *
419
	 * @return $this
420
	 */
421 96
	public function sync_original() {
422 96
		$this->original = $this->attributes;
423
424 96
		if ( $this->attributes[ self::OBJECT_KEY ] ) {
425 9
			$this->original[ self::OBJECT_KEY ] = clone $this->attributes[ self::OBJECT_KEY ];
426 9
		}
427
428 96
		foreach ( $this->original[ self::TABLE_KEY ] as $key => $item ) {
429 9
			if ( is_object( $item ) ) {
430 9
				$this->original[ $key ] = clone $item;
431 9
			}
432 96
		}
433
434 96
		return $this;
435
	}
436
437
	/**
438
	 * Checks if a given attribute is mass-fillable.
439
	 *
440
	 * Returns true if the attribute can be filled, false if it can't.
441
	 *
442
	 * @param string $name
443
	 *
444
	 * @return bool
445
	 */
446 96
	private function is_fillable( $name ) {
447
		// If this model isn't guarded, everything is fillable.
448 96
		if ( ! $this->is_guarded ) {
449 96
			return true;
450
		}
451
452
		// If it's in the fillable array, then it's fillable.
453 18
		if ( in_array( $name, $this->fillable ) ) {
454 12
			return true;
455
		}
456
457
		// If it's explicitly guarded, then it's not fillable.
458 9
		if ( in_array( $name, $this->guarded ) ) {
459 9
			return false;
460
		}
461
462
		// If fillable hasn't been defined, then everything else fillable.
463
		return ! $this->fillable;
464
	}
465
466
	/**
467
	 * Overrides the current WordPress object with a provided one.
468
	 *
469
	 * Resets the post's default values and stores it in the attributes.
470
	 *
471
	 * @param WP_Post|WP_Term|null $value
472
	 *
473
	 * @return $this
474
	 */
475 27
	private function override_wp_object( $value ) {
476 27
		if ( is_object( $value ) ) {
477 27
			$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $value );
478 27
		} else {
479
			$this->attributes[ self::OBJECT_KEY ] = null;
480
481
			if ( $this->uses_wp_object() ) {
482
				$this->create_wp_object();
483
			}
484
		}
485
486 27
		return $this;
487
	}
488
489
	/**
490
	 * Overrides the current table attributes array with a provided one.
491
	 *
492
	 * @param array $value
493
	 *
494
	 * @return $this
495
	 */
496 6
	private function override_table( array $value ) {
497 6
		$this->attributes[ self::TABLE_KEY ] = $value;
498
499 6
		return $this;
500
	}
501
502
	/**
503
	 * Create and set with a new blank post.
504
	 *
505
	 * Creates a new WP_Post object, assigns it the default attributes,
506
	 * and stores it in the attributes.
507
	 *
508
	 * @throws LogicException
509
	 */
510 60
	private function create_wp_object() {
511 60
		switch ( true ) {
512 60
			case $this instanceof UsesWordPressPost:
513 60
				$object = new WP_Post( (object) array() );
514 60
				break;
515
			case $this instanceof UsesWordPressTerm:
516
				$object = new WP_Term( (object) array() );
517
				break;
518
			default:
519
				throw new LogicException;
520
				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...
521
		}
522
523 60
		$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $object );
524 60
	}
525
526
	/**
527
	 * Enforces values on the post that can't change.
528
	 *
529
	 * Primarily, this is used to make sure the post_type always maps
530
	 * to the model's "$type" property, but this can all be overridden
531
	 * by the developer to enforce other values in the model.
532
	 *
533
	 * @param object $object
534
	 *
535
	 * @return object
536
	 */
537 60
	protected function set_wp_object_constants( $object ) {
538 60
		if ( $this instanceof UsesWordPressPost ) {
539 60
			$object->post_type = static::get_post_type();
0 ignored issues
show
Bug introduced by
The method get_post_type() does not seem to exist on object<Intraxia\Jaxion\Axolotl\Model>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
540 60
		}
541
542 60
		if ( $this instanceof UsesWordPressTerm ) {
543
			$object->taxonomy = static::get_taxonomy();
0 ignored issues
show
Bug introduced by
The method get_taxonomy() does not seem to exist on object<Intraxia\Jaxion\Axolotl\Model>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
544
		}
545
546 60
		return $object;
547
	}
548
549
	/**
550
	 * Magic __get method.
551
	 *
552
	 * Passes the name and value to get_attribute, which is where the magic happens.
553
	 *
554
	 * @param string $name
555
	 *
556
	 * @return mixed
557
	 */
558 24
	public function __get( $name ) {
559 24
		return $this->get_attribute( $name );
560
	}
561
562
	/**
563
	 * Retrieves the model attribute.
564
	 *
565
	 * @param string $name
566
	 *
567
	 * @return mixed
568
	 *
569
	 * @throws PropertyDoesNotExistException If property isn't found.
570
	 */
571 51
	public function get_attribute( $name ) {
572 51
		if ( $method = $this->has_map_method( $name ) ) {
573 24
			return $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()};
574
		}
575
576 39
		if ( $method = $this->has_compute_method( $name ) ) {
577 12
			return $this->{$method}();
578
		}
579
580 36
		if ( isset( $this->attributes[ self::TABLE_KEY ][ $name ] ) ) {
581 33
			return $this->attributes[ self::TABLE_KEY ][ $name ];
582
		}
583
584 3
		if ( isset( $this->defaults[ $name ] ) ) {
585
			return $this->defaults[ $name ];
586
		}
587
588 3
		throw new PropertyDoesNotExistException( $name );
589
	}
590
591
	/**
592
	 * Retrieve the model's original attribute value.
593
	 *
594
	 * @param string $name
595
	 *
596
	 * @return mixed
597
	 *
598
	 * @throws PropertyDoesNotExistException If property isn't found.
599
	 */
600 6
	public function get_original_attribute( $name ) {
601 6
		$original_attributes = $this->original;
602
603 6
		if ( ! is_object( $original_attributes[ static::OBJECT_KEY ] ) ) {
604
			unset( $original_attributes[ static::OBJECT_KEY ] );
605
		}
606
607 6
		$original = new static( $original_attributes );
608
609 6
		return $original->get_attribute( $name );
610
	}
611
612
	/**
613
	 * Fetches the Model's primary ID, depending on the model
614
	 * implementation.
615
	 *
616
	 * @return int
617
	 *
618
	 * @throws LogicException
619
	 */
620
	public function get_primary_id() {
621
		if ( $this instanceof UsesWordPressPost ) {
622
			return $this->get_underlying_wp_object()->ID;
623
		}
624
625
		if ( $this instanceof UsesWordPressTerm ) {
626
			return $this->get_underlying_wp_object()->term_id;
627
		}
628
629
		// Model w/o wp_object not yet supported.
630
		throw new LogicException;
631
	}
632
633
	/**
634
	 * Checks whether the attribute has a map method.
635
	 *
636
	 * This is used to determine whether the attribute maps to a
637
	 * property on the underlying WP_Post object. Returns the
638
	 * method if one exists, returns false if it doesn't.
639
	 *
640
	 * @param string $name
641
	 *
642
	 * @return false|string
643
	 */
644 96
	protected function has_map_method( $name ) {
645 96
		if ( method_exists( $this, $method = "map_{$name}" ) ) {
646 60
			return $method;
647
		}
648
649 96
		return false;
650
	}
651
652
	/**
653
	 * Checks whether the attribute has a compute method.
654
	 *
655
	 * This is used to determine if the attribute should be computed
656
	 * from other attributes.
657
	 *
658
	 * @param string $name
659
	 *
660
	 * @return false|string
661
	 */
662 51
	protected function has_compute_method( $name ) {
663 51
		if ( method_exists( $this, $method = "compute_{$name}" ) ) {
664 21
			return $method;
665
		}
666
667 48
		return false;
668
	}
669
670
	/**
671
	 * Clears all the current attributes from the model.
672
	 *
673
	 * This does not touch the model's original attributes, and will
674
	 * only clear fillable attributes, unless the model is unguarded.
675
	 *
676
	 * @return $this
677
	 */
678 96
	public function clear() {
679 96
		$keys = array_merge(
680 96
			$this->get_table_keys(),
681 96
			$this->get_wp_object_keys()
682 96
		);
683
684 96
		foreach ( $keys as $key ) {
685
			try {
686 96
				$this->set_attribute( $key, null );
687 96
			} catch ( GuardedPropertyException $e ) {
688
				// We won't clear out guarded attributes.
689
			}
690 96
		}
691
692 96
		return $this;
693
	}
694
695
	/**
696
	 * Unguards the model.
697
	 *
698
	 * Sets the model to be unguarded, allowing the filling of
699
	 * guarded attributes.
700
	 */
701 96
	public function unguard() {
702 96
		$this->is_guarded = false;
703 96
	}
704
705
	/**
706
	 * Reguards the model.
707
	 *
708
	 * Sets the model to be guarded, preventing filling of
709
	 * guarded attributes.
710
	 */
711 96
	public function reguard() {
712 96
		$this->is_guarded = true;
713 96
	}
714
715
	/**
716
	 * Retrieves all the compute methods on the model.
717
	 *
718
	 * @return array
719
	 */
720 9
	protected function get_compute_methods() {
721 9
		$methods = get_class_methods( get_called_class() );
722
		$methods = array_filter( $methods, function ( $method ) {
723 9
			return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
724 9
		} );
725 9
		$methods = array_map( function ( $method ) {
726 6
			return substr( $method, strlen( 'compute_' ) );
727 9
		}, $methods );
728
729 9
		return $methods;
730
	}
731
732
	/**
733
	 * Sets up the memo array for the creating model.
734
	 */
735 96
	private function maybe_boot() {
736 96
		if ( ! isset( self::$memo[ get_called_class() ] ) ) {
737 9
			self::$memo[ get_called_class() ] = array();
738 9
		}
739 96
	}
740
741
	/**
742
	 * Whether this Model uses an underlying WordPress object.
743
	 *
744
	 * @return bool
745
	 */
746 96
	protected function uses_wp_object() {
747 96
		return $this instanceof UsesWordPressPost ||
748 96
			$this instanceof UsesWordPressTerm;
749
	}
750
}
751