Completed
Push — master ( b532f3...334f64 )
by James
03:15
created

Model::refresh()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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 69
	public function __construct( array $attributes = array() ) {
118 69
		$this->maybe_boot();
119 69
		$this->sync_original();
120
121 69
		if ( $this->uses_wp_object() ) {
122 66
			$this->create_wp_object();
123 66
		}
124
125 69
		$this->unguard();
126 69
		$this->refresh( $attributes );
127 69
		$this->reguard();
128 69
	}
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 69
	public function refresh( array $attributes ) {
141 69
		$this->clear();
142
143 69
		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 69
	public function merge( array $attributes ) {
154 69
		foreach ( $attributes as $name => $value ) {
155 39
			$this->set_attribute( $name, $value );
156 69
		}
157
158 69
		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 Exception
274
	 * @throws GuardedPropertyException
275
	 */
276 69
	public function set_attribute( $name, $value ) {
277 69
		if ( self::OBJECT_KEY === $name ) {
278 27
			return $this->override_wp_object( $value );
279
		}
280
281 69
		if ( self::TABLE_KEY === $name ) {
282 6
			return $this->override_table( $value );
283
		}
284
285 69
		if ( ! $this->is_fillable( $name ) ) {
286 9
			throw new GuardedPropertyException;
287
		}
288
289 69
		if ( $method = $this->has_map_method( $name ) ) {
290 66
			$this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()} = $value;
291 66
		} else {
292 69
			$this->attributes[ self::TABLE_KEY ][ $name ] = $value;
293
		}
294
295 69
		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 9
			);
314
	}
315
316
	/**
317
	 * Retrieves the attribute keys that aren't mapped to a post.
318
	 *
319
	 * @return array
320
	 */
321 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...
322 69
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
323 66
			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 ) &&
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...
330 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...
331 9
			) {
332 9
				$keys[] = $key;
333 9
			}
334 9
		}
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 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...
345 69
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
346 66
			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 ) ) {
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...
353 6
				$keys[] = $key;
354 6
			}
355 9
		}
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 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...
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 ) ) {
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...
374 3
				$keys[] = $key;
375 3
			}
376 3
		}
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 ) {
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...
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 6
			}
394 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...
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 3
				}
400 3
			}
401 3
		} 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 3
			}
406
		}
407
408
		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 69
	public function sync_original() {
423 69
		$this->original = $this->attributes;
424
425 69
		if ( $this->attributes[ self::OBJECT_KEY ] ) {
426 9
			$this->original[ self::OBJECT_KEY ] = clone $this->attributes[ self::OBJECT_KEY ];
427 9
		}
428
429 69
		foreach ( $this->original[ self::TABLE_KEY ] as $key => $item ) {
430 9
			if ( is_object( $item ) ) {
431 9
				$this->original[ $key ] = clone $item;
432 9
			}
433 69
		}
434
435 69
		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 69
	private function is_fillable( $name ) {
448
		// If this model isn't guarded, everything is fillable.
449 69
		if ( ! $this->is_guarded ) {
450 69
			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 27
		} 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 66
	private function create_wp_object() {
512 66
		switch ( true ) {
513 66
			case $this instanceof UsesWordPressPost:
514 66
				$object = new WP_Post( (object) array() );
515 66
				break;
516
			case $this instanceof UsesWordPressTerm:
517
				$object = new WP_Term( (object) array() );
518
				break;
519
			default:
520
				throw new LogicException;
521
				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...
522
		}
523
524 66
		$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $object );
525 66
	}
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 66
	protected function set_wp_object_constants( $object ) {
539 66
		if ( $this instanceof UsesWordPressPost ) {
540 66
			$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...
541 66
		}
542
543 66
		if ( $this instanceof UsesWordPressTerm ) {
544
			$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...
545
		}
546
547 66
		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 18
	public function __get( $name ) {
560 18
		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 45
	public function get_attribute( $name ) {
573 45
		if ( $method = $this->has_map_method( $name ) ) {
574 33
			return $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()};
575
		}
576
577 27
		if ( $method = $this->has_compute_method( $name ) ) {
578 15
			return $this->{$method}();
579
		}
580
581 24
		if ( isset( $this->attributes[ self::TABLE_KEY ][ $name ] ) ) {
582 21
			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
		throw new PropertyDoesNotExistException( $name );
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
		try {
611 6
			return $original->get_attribute( $name );
612
		} catch ( Exception $exception ) {
613
			return null;
614
		}
615
	}
616
617
	/**
618
	 * Fetches the Model's primary ID, depending on the model
619
	 * implementation.
620
	 *
621
	 * @return int
622
	 *
623
	 * @throws LogicException
624
	 */
625
	public function get_primary_id() {
626
		if ( $this instanceof UsesWordPressPost ) {
627
			return $this->get_underlying_wp_object()->ID;
628
		}
629
630
		if ( $this instanceof UsesWordPressTerm ) {
631
			return $this->get_underlying_wp_object()->term_id;
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 69
	protected function has_map_method( $name ) {
650 69
		if ( method_exists( $this, $method = "map_{$name}" ) ) {
651 66
			return $method;
652
		}
653
654 69
		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 39
	protected function has_compute_method( $name ) {
668 39
		if ( method_exists( $this, $method = "compute_{$name}" ) ) {
669 24
			return $method;
670
		}
671
672 36
		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 69
	public function clear() {
684 69
		$keys = array_merge(
685 69
			$this->get_table_keys(),
686 69
			$this->get_wp_object_keys()
687 69
		);
688
689 69
		foreach ( $keys as $key ) {
690
			try {
691 69
				$this->set_attribute( $key, null );
692 69
			} catch ( GuardedPropertyException $e ) {
693
				// We won't clear out guarded attributes.
694
			}
695 69
		}
696
697 69
		return $this;
698
	}
699
700
	/**
701
	 * Unguards the model.
702
	 *
703
	 * Sets the model to be unguarded, allowing the filling of
704
	 * guarded attributes.
705
	 */
706 69
	public function unguard() {
707 69
		$this->is_guarded = false;
708 69
	}
709
710
	/**
711
	 * Reguards the model.
712
	 *
713
	 * Sets the model to be guarded, preventing filling of
714
	 * guarded attributes.
715
	 */
716 69
	public function reguard() {
717 69
		$this->is_guarded = true;
718 69
	}
719
720
	/**
721
	 * Retrieves all the compute methods on the model.
722
	 *
723
	 * @return array
724
	 */
725 9
	protected function get_compute_methods() {
726 9
		$methods = get_class_methods( get_called_class() );
727
		$methods = array_filter( $methods, function ( $method ) {
728 9
			return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
729 9
		} );
730 9
		$methods = array_map( function ( $method ) {
731 6
			return substr( $method, strlen( 'compute_' ) );
732 9
		}, $methods );
733
734 9
		return $methods;
735
	}
736
737
	/**
738
	 * Sets up the memo array for the creating model.
739
	 */
740 69
	private function maybe_boot() {
741 69
		if ( ! isset( self::$memo[ get_called_class() ] ) ) {
742 9
			self::$memo[ get_called_class() ] = array();
743 9
		}
744 69
	}
745
746
	/**
747
	 * Whether this Model uses an underlying WordPress object.
748
	 *
749
	 * @return bool
750
	 */
751 69
	protected function uses_wp_object() {
752 69
		return $this instanceof UsesWordPressPost ||
753 69
			$this instanceof UsesWordPressTerm;
754
	}
755
}
756