Completed
Pull Request — master (#15)
by James
03:45
created

Model::get_original_table_attributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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 36
	public function __construct( array $attributes = array() ) {
118 36
		$this->maybe_boot();
119 36
		$this->sync_original();
120
121 36
		if ( $this->uses_wp_object() ) {
122 33
			$this->create_wp_object();
123 33
		}
124
125 36
		$this->unguard();
126 36
		$this->refresh( $attributes );
127 36
		$this->reguard();
128 36
	}
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 36
	public function refresh( array $attributes ) {
141 36
		$this->clear();
142
143 36
		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 36
	public function merge( array $attributes ) {
154 36
		foreach ( $attributes as $name => $value ) {
155 6
			$this->set_attribute( $name, $value );
156 36
		}
157
158 36
		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 6
	public function get_table_attributes() {
170 6
		return $this->attributes[ self::TABLE_KEY ];
171
	}
172
173
	/**
174
	 * Get the model's original attributes.
175
	 *
176
	 * @return array
177
	 */
178
	public function get_original_table_attributes() {
179
		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
	public function get_changed_table_attributes() {
190
		$changed = array();
191
192
		foreach ( $this->get_table_attributes() as $key => $value ) {
193
			if ( $value !==
194
				 $this->get_original_attribute( $key )
195
			) {
196
				$changed[ $key ] = $value;
197
			}
198
		}
199
200
		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 12
	public function get_underlying_wp_object() {
212 12
		if ( isset( $this->attributes[ self::OBJECT_KEY ] ) ) {
213 9
			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
	public function get_original_underlying_wp_object() {
225
		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
	public function get_changed_wp_object_attributes() {
236
		$changed = array();
237
238
		foreach ( $this->get_wp_object_keys() as $key ) {
239
			if ( $this->get_attribute( $key ) !==
240
				 $this->get_original_attribute( $key )
241
			) {
242
				$changed[ $key ] = $this->get_attribute( $key );
243
			}
244
		}
245
246
		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 36
	public function set_attribute( $name, $value ) {
276 36
		if ( self::OBJECT_KEY === $name ) {
277 3
			return $this->override_wp_object( $value );
278
		}
279
280 36
		if ( self::TABLE_KEY === $name ) {
281
			return $this->override_table( $value );
282
		}
283
284 36
		if ( ! $this->is_fillable( $name ) ) {
285 6
			throw new GuardedPropertyException;
286
		}
287
288 36
		if ( $method = $this->has_map_method( $name ) ) {
289 33
			$this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()} = $value;
290 33
		} else {
291 36
			$this->attributes[ self::TABLE_KEY ][ $name ] = $value;
292
		}
293
294 36
		return $this;
295
	}
296
297
	/**
298
	 * Retrieves all the attribute keys for the model.
299
	 *
300
	 * @return array
301
	 */
302 12
	public function get_attribute_keys() {
303 12
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
304 12
			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 36 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 36
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
322 30
			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 36 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 36
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
345 30
			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
	public function serialize() {
386
		$attributes = array();
387
388
		if ( $this->visible ) {
389
			// If visible attributes are set, we'll only reveal those.
390
			foreach ( $this->visible as $key ) {
391
				$attributes[ $key ] = $this->get_attribute( $key );
392
			}
393
		} elseif ( $this->hidden ) {
394
			// If hidden attributes are set, we'll grab everything and hide those.
395
			foreach ( $this->get_attribute_keys() as $key ) {
396
				if ( ! in_array( $key, $this->hidden ) ) {
397
					$attributes[ $key ] = $this->get_attribute( $key );
398
				}
399
			}
400
		} else {
401
			// If nothing is hidden/visible, we'll grab and reveal everything.
402
			foreach ( $this->get_attribute_keys() as $key ) {
403
				$attributes[ $key ] = $this->get_attribute( $key );
404
			}
405
		}
406
407
		return array_map( function ( $attribute ) {
408
			if ( $attribute instanceof Serializes ) {
409
				return $attribute->serialize();
410
			}
411
412
			return $attribute;
413
		}, $attributes );
414
	}
415
416
	/**
417
	 * Syncs the current attributes to the model's original.
418
	 *
419
	 * @return $this
420
	 */
421 36
	public function sync_original() {
422 36
		$this->original = $this->attributes;
423
424 36
		if ( $this->attributes[ self::OBJECT_KEY ] ) {
425
			$this->original[ self::OBJECT_KEY ] = clone $this->attributes[ self::OBJECT_KEY ];
426
		}
427
428 36
		foreach ( $this->original[ self::TABLE_KEY ] as $key => $item ) {
429
			if ( is_object( $item ) ) {
430
				$this->original[ $key ] = clone $item;
431
			}
432 36
		}
433
434 36
		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 36
	private function is_fillable( $name ) {
447
		// If this model isn't guarded, everything is fillable.
448 36
		if ( ! $this->is_guarded ) {
449 36
			return true;
450
		}
451
452
		// If it's in the fillable array, then it's fillable.
453 12
		if ( in_array( $name, $this->fillable ) ) {
454 6
			return true;
455
		}
456
457
		// If it's explicitly guarded, then it's not fillable.
458 6
		if ( in_array( $name, $this->guarded ) ) {
459 6
			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 3
	private function override_wp_object( $value ) {
476 3
		if ( is_object( $value ) ) {
477 3
			$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $value );
478 3
		} else {
479
			$this->attributes[ self::OBJECT_KEY ] = null;
480
481
			if ( $this->uses_wp_object() ) {
482
				$this->create_wp_object();
483
			}
484
		}
485
486 3
		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
	private function override_table( array $value ) {
497
		$this->attributes[ self::TABLE_KEY ] = $value;
498
499
		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 33
	private function create_wp_object() {
511 33
		switch ( true ) {
512 33
			case $this instanceof UsesWordPressPost:
513 33
				$object = new WP_Post( (object) array() );
514 33
				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 33
		$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $object );
524 33
	}
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 33
	protected function set_wp_object_constants( $object ) {
538 33
		if ( $this instanceof UsesWordPressPost ) {
539 33
			$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 33
		}
541
542 33
		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 33
		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 12
	public function __get( $name ) {
559 12
		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 15
	public function get_attribute( $name ) {
572 15
		if ( $method = $this->has_map_method( $name ) ) {
573 9
			return $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()};
574
		}
575
576 6
		if ( $method = $this->has_compute_method( $name ) ) {
577
			return $this->{$method}();
578
		}
579
580 6
		if ( isset( $this->attributes[ self::TABLE_KEY ][ $name ] ) ) {
581 6
			return $this->attributes[ self::TABLE_KEY ][ $name ];
582
		}
583
584
		if ( isset( $this->defaults[ $name ] ) ) {
585
			return $this->defaults[ $name ];
586
		}
587
588
		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
	public function get_original_attribute( $name ) {
601
		$original_attributes = $this->original;
602
603
		if ( ! is_object( $original_attributes[ static::OBJECT_KEY ] ) ) {
604
			unset( $original_attributes[ static::OBJECT_KEY ] );
605
		}
606
607
		$original = new static( $original_attributes );
608
609
		try {
610
			return $original->get_attribute( $name );
611
		} catch ( Exception $exception ) {
612
			return null;
613
		}
614
	}
615
616
	/**
617
	 * Fetches the Model's primary ID, depending on the model
618
	 * implementation.
619
	 *
620
	 * @return int
621
	 *
622
	 * @throws LogicException
623
	 */
624
	public function get_primary_id() {
625
		if ( $this instanceof UsesWordPressPost ) {
626
			return $this->get_underlying_wp_object()->ID;
627
		}
628
629
		if ( $this instanceof UsesWordPressTerm ) {
630
			return $this->get_underlying_wp_object()->term_id;
631
		}
632
633
		// Model w/o wp_object not yet supported.
634
		throw new LogicException;
635
	}
636
637
	/**
638
	 * Checks whether the attribute has a map method.
639
	 *
640
	 * This is used to determine whether the attribute maps to a
641
	 * property on the underlying WP_Post object. Returns the
642
	 * method if one exists, returns false if it doesn't.
643
	 *
644
	 * @param string $name
645
	 *
646
	 * @return false|string
647
	 */
648 36
	protected function has_map_method( $name ) {
649 36
		if ( method_exists( $this, $method = "map_{$name}" ) ) {
650 33
			return $method;
651
		}
652
653 36
		return false;
654
	}
655
656
	/**
657
	 * Checks whether the attribute has a compute method.
658
	 *
659
	 * This is used to determine if the attribute should be computed
660
	 * from other attributes.
661
	 *
662
	 * @param string $name
663
	 *
664
	 * @return false|string
665
	 */
666 15
	protected function has_compute_method( $name ) {
667 15
		if ( method_exists( $this, $method = "compute_{$name}" ) ) {
668 9
			return $method;
669
		}
670
671 15
		return false;
672
	}
673
674
	/**
675
	 * Clears all the current attributes from the model.
676
	 *
677
	 * This does not touch the model's original attributes, and will
678
	 * only clear fillable attributes, unless the model is unguarded.
679
	 *
680
	 * @return $this
681
	 */
682 36
	public function clear() {
683 36
		$keys = array_merge(
684 36
			$this->get_table_keys(),
685 36
			$this->get_wp_object_keys()
686 36
		);
687
688 36
		foreach ( $keys as $key ) {
689
			try {
690 36
				$this->set_attribute( $key, null );
691 36
			} catch ( GuardedPropertyException $e ) {
692
				// We won't clear out guarded attributes.
693
			}
694 36
		}
695
696 36
		return $this;
697
	}
698
699
	/**
700
	 * Unguards the model.
701
	 *
702
	 * Sets the model to be unguarded, allowing the filling of
703
	 * guarded attributes.
704
	 */
705 36
	public function unguard() {
706 36
		$this->is_guarded = false;
707 36
	}
708
709
	/**
710
	 * Reguards the model.
711
	 *
712
	 * Sets the model to be guarded, preventing filling of
713
	 * guarded attributes.
714
	 */
715 36
	public function reguard() {
716 36
		$this->is_guarded = true;
717 36
	}
718
719
	/**
720
	 * Retrieves all the compute methods on the model.
721
	 *
722
	 * @return array
723
	 */
724 9
	protected function get_compute_methods() {
725 9
		$methods = get_class_methods( get_called_class() );
726
		$methods = array_filter( $methods, function ( $method ) {
727 9
			return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
728 9
		} );
729 9
		$methods = array_map( function ( $method ) {
730 6
			return substr( $method, strlen( 'compute_' ) );
731 9
		}, $methods );
732
733 9
		return $methods;
734
	}
735
736
	/**
737
	 * Sets up the memo array for the creating model.
738
	 */
739 36
	private function maybe_boot() {
740 36
		if ( ! isset( self::$memo[ get_called_class() ] ) ) {
741 9
			self::$memo[ get_called_class() ] = array();
742 9
		}
743 36
	}
744
745
	/**
746
	 * Whether this Model uses an underlying WordPress object.
747
	 *
748
	 * @return bool
749
	 */
750 36
	protected function uses_wp_object() {
751 36
		return $this instanceof UsesWordPressPost ||
752 36
			$this instanceof UsesWordPressTerm;
753
	}
754
}
755