Model   F
last analyzed

Complexity

Total Complexity 90

Size/Duplication

Total Lines 736
Duplicated Lines 6.39 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 89.84%

Importance

Changes 0
Metric Value
wmc 90
lcom 1
cbo 2
dl 47
loc 736
ccs 221
cts 246
cp 0.8984
rs 1.864
c 0
b 0
f 0

34 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A refresh() 0 5 1
A merge() 0 7 2
A get_table_attributes() 0 3 1
A get_original_table_attributes() 0 3 1
A get_changed_table_attributes() 0 13 3
A get_underlying_wp_object() 0 7 2
A get_original_underlying_wp_object() 0 3 1
A get_changed_wp_object_attributes() 0 13 3
A __set() 0 3 1
A set_attribute() 0 21 5
A get_attribute_keys() 0 12 2
A get_table_keys() 17 17 5
A get_wp_object_keys() 15 15 4
A get_computed_keys() 15 15 4
B serialize() 0 30 8
A sync_original() 0 15 4
A is_fillable() 0 19 4
A override_wp_object() 0 13 3
A override_table() 0 5 1
A create_wp_object() 0 15 3
A set_wp_object_constants() 0 11 3
A __get() 0 3 1
A get_attribute() 0 19 5
A get_original_attribute() 0 11 2
A get_primary_id() 0 16 4
A has_map_method() 0 7 2
A has_compute_method() 0 7 2
A clear() 0 19 4
A unguard() 0 3 1
A reguard() 0 3 1
A maybe_boot() 0 5 2
A uses_wp_object() 0 4 2
A get_compute_methods() 0 11 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Model, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Intraxia\Jaxion\Axolotl;
3
4
use Exception;
5
use Intraxia\Jaxion\Contract\Axolotl\Serializes;
6
use Intraxia\Jaxion\Contract\Axolotl\UsesCustomTable;
7
use Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost;
8
use Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm;
9
use LogicException;
10
use WP_Post;
11
use WP_Term;
12
13
/**
14
 * Class Model
15
 *
16
 * Shared model methods and properties, allowing models
17
 * to transparently map some attributes to an underlying WP_Post
18
 * object and others to postmeta or a custom table.
19
 *
20
 * @package    Intraxia\Jaxion
21
 * @subpackage Axolotl
22
 * @since      0.1.0
23
 */
24
abstract class Model implements Serializes {
25
	/**
26
	 * Table attribute key.
27
	 */
28
	const TABLE_KEY = '@@table';
29
30
	/**
31
	 * Object attribute key.
32
	 */
33
	const OBJECT_KEY = '@@object';
34
35
	/**
36
	 * Memoized values for class methods.
37
	 *
38
	 * @var array
39
	 */
40
	private static $memo = array();
41
42
	/**
43
	 * Model attributes.
44
	 *
45
	 * @var array
46
	 */
47
	private $attributes = array(
48
		self::TABLE_KEY  => array(),
49
		self::OBJECT_KEY => null,
50
	);
51
52
	/**
53
	 * Model's original attributes.
54
	 *
55
	 * @var array
56
	 */
57
	private $original = array(
58
		self::TABLE_KEY  => array(),
59
		self::OBJECT_KEY => null,
60
	);
61
62
	/**
63
	 * Default attribute values.
64
	 *
65
	 * @var array
66
	 */
67
	protected $defaults = array();
68
69
	/**
70
	 * Properties which are allowed to be set on the model.
71
	 *
72
	 * If this array is empty, any attributes can be set on the model.
73
	 *
74
	 * @var string[]
75
	 */
76
	protected $fillable = array();
77
78
	/**
79
	 * Properties which cannot be automatically filled on the model.
80
	 *
81
	 * If the model is unguarded, these properties can be filled.
82
	 *
83
	 * @var array
84
	 */
85
	protected $guarded = array();
86
87
	/**
88
	 * Properties which should not be serialized.
89
	 *
90
	 * @var array
91
	 */
92
	protected $hidden = array();
93
94
	/**
95
	 * Properties which should be serialized.
96
	 *
97
	 * @var array
98
	 */
99
	protected $visible = array();
100
101
	/**
102
	 * Whether the model's properties are guarded.
103
	 *
104
	 * When false, allows guarded properties to be filled.
105
	 *
106
	 * @var bool
107
	 */
108
	protected $is_guarded = true;
109
110
	/**
111
	 * Constructs a new model with provided attributes.
112
	 *
113
	 * If self::OBJECT_KEY is passed as one of the attributes, the underlying post
114
	 * will be overwritten.
115
	 *
116
	 * @param array <string, mixed> $attributes
117
	 */
118 96
	public function __construct( array $attributes = array() ) {
119 96
		$this->maybe_boot();
120 96
		$this->sync_original();
121
122 96
		if ( $this->uses_wp_object() ) {
123 60
			$this->create_wp_object();
124 40
		}
125
126 96
		$this->unguard();
127 96
		$this->refresh( $attributes );
128 96
		$this->reguard();
129 96
	}
130
131
	/**
132
	 * Refreshes the model's current attributes with the provided array.
133
	 *
134
	 * The model's attributes will match what was provided in the array,
135
	 * and any attributes not passed
136
	 *
137
	 * @param array $attributes
138
	 *
139
	 * @return $this
140
	 */
141 96
	public function refresh( array $attributes ) {
142 96
		$this->clear();
143
144 96
		return $this->merge( $attributes );
145
	}
146
147
	/**
148
	 * Merges the provided attributes with the provided array.
149
	 *
150
	 * @param array $attributes
151
	 *
152
	 * @return $this
153
	 */
154 96
	public function merge( array $attributes ) {
155 96
		foreach ( $attributes as $name => $value ) {
156 45
			$this->set_attribute( $name, $value );
157 64
		}
158
159 96
		return $this;
160
	}
161
162
	/**
163
	 * Get the model's table attributes.
164
	 *
165
	 * Returns the array of for the model that will either need to be
166
	 * saved in postmeta or a separate table.
167
	 *
168
	 * @return array
169
	 */
170 12
	public function get_table_attributes() {
171 12
		return $this->attributes[ self::TABLE_KEY ];
172
	}
173
174
	/**
175
	 * Get the model's original attributes.
176
	 *
177
	 * @return array
178
	 */
179 6
	public function get_original_table_attributes() {
180 6
		return $this->original[ self::TABLE_KEY ];
181
	}
182
183
	/**
184
	 * Retrieve an array of the attributes on the model
185
	 * that have changed compared to the model's
186
	 * original data.
187
	 *
188
	 * @return array
189
	 */
190 3
	public function get_changed_table_attributes() {
191 3
		$changed = array();
192
193 3
		foreach ( $this->get_table_attributes() as $key => $value ) {
194
			if ( $value !==
195 3
				 $this->get_original_attribute( $key )
196 2
			) {
197 3
				$changed[ $key ] = $value;
198 2
			}
199 2
		}
200
201 3
		return $changed;
202
	}
203
204
	/**
205
	 * Get the model's underlying post.
206
	 *
207
	 * Returns the underlying WP_Post object for the model, representing
208
	 * the data that will be save in the wp_posts table.
209
	 *
210
	 * @return false|WP_Post|WP_Term
211
	 */
212 18
	public function get_underlying_wp_object() {
213 18
		if ( isset( $this->attributes[ self::OBJECT_KEY ] ) ) {
214 15
			return $this->attributes[ self::OBJECT_KEY ];
215
		}
216
217 3
		return false;
218
	}
219
220
	/**
221
	 * Get the model's original underlying post.
222
	 *
223
	 * @return WP_Post
224
	 */
225 6
	public function get_original_underlying_wp_object() {
226 6
		return $this->original[ self::OBJECT_KEY ];
227
	}
228
229
	/**
230
	 * Get the model attributes on the WordPress object
231
	 * that have changed compared to the model's
232
	 * original attributes.
233
	 *
234
	 * @return array
235
	 */
236 3
	public function get_changed_wp_object_attributes() {
237 3
		$changed = array();
238
239 3
		foreach ( $this->get_wp_object_keys() as $key ) {
240 3
			if ( $this->get_attribute( $key ) !==
241 3
				 $this->get_original_attribute( $key )
242 2
			) {
243 3
				$changed[ $key ] = $this->get_attribute( $key );
244 2
			}
245 2
		}
246
247 3
		return $changed;
248
	}
249
250
	/**
251
	 * Magic __set method.
252
	 *
253
	 * Passes the name and value to set_attribute, which is where the magic happens.
254
	 *
255
	 * @param string $name
256
	 * @param mixed  $value
257
	 */
258 6
	public function __set( $name, $value ) {
259 6
		$this->set_attribute( $name, $value );
260 6
	}
261
262
	/**
263
	 * Sets the model attributes.
264
	 *
265
	 * Checks whether the model attribute can be set, check if it
266
	 * maps to the WP_Post property, otherwise, assigns it to the
267
	 * table attribute array.
268
	 *
269
	 * @param string $name
270
	 * @param mixed  $value
271
	 *
272
	 * @return $this
273
	 *
274
	 * @throws GuardedPropertyException
275
	 */
276 96
	public function set_attribute( $name, $value ) {
277 96
		if ( self::OBJECT_KEY === $name ) {
278 27
			return $this->override_wp_object( $value );
279
		}
280
281 96
		if ( self::TABLE_KEY === $name ) {
282 6
			return $this->override_table( $value );
283
		}
284
285 96
		if ( ! $this->is_fillable( $name ) ) {
286 9
			throw new GuardedPropertyException( $name );
287
		}
288
289 96
		if ( $method = $this->has_map_method( $name ) ) {
290 60
			$this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()} = $value;
291 40
		} else {
292 96
			$this->attributes[ self::TABLE_KEY ][ $name ] = $value;
293
		}
294
295 96
		return $this;
296
	}
297
298
	/**
299
	 * Retrieves all the attribute keys for the model.
300
	 *
301
	 * @return array
302
	 */
303 18
	public function get_attribute_keys() {
304 18
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
305 18
			return self::$memo[ get_called_class() ][ __METHOD__ ];
306
		}
307
308 9
		return self::$memo[ get_called_class() ][ __METHOD__ ]
309 9
			= array_merge(
310 9
				$this->fillable,
311 9
				$this->guarded,
312 9
				$this->get_compute_methods()
313 6
			);
314
	}
315
316
	/**
317
	 * Retrieves the attribute keys that aren't mapped to a post.
318
	 *
319
	 * @return array
320
	 */
321 96 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 96
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
323 93
			return self::$memo[ get_called_class() ][ __METHOD__ ];
324
		}
325
326 9
		$keys = array();
327
328 9
		foreach ( $this->get_attribute_keys() as $key ) {
329 9
			if ( ! $this->has_map_method( $key ) &&
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 6
			) {
332 9
				$keys[] = $key;
333 6
			}
334 6
		}
335
336 9
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
337
	}
338
339
	/**
340
	 * Retrieves the attribute keys that are mapped to a post.
341
	 *
342
	 * @return array
343
	 */
344 96 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 96
		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
346 93
			return self::$memo[ get_called_class() ][ __METHOD__ ];
347
		}
348
349 9
		$keys = array();
350
351 9
		foreach ( $this->get_attribute_keys() as $key ) {
352 9
			if ( $this->has_map_method( $key ) ) {
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 4
			}
355 6
		}
356
357 9
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
358
	}
359
360
	/**
361
	 * Returns the model's keys that are computed at call time.
362
	 *
363
	 * @return array
364
	 */
365 3 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 2
			}
376 2
		}
377
378 3
		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
379
	}
380
381
	/**
382
	 * Serializes the model's public data into an array.
383
	 *
384
	 * @return array
385
	 */
386 12
	public function serialize() {
387 12
		$attributes = array();
388
389 12
		if ( $this->visible ) {
390
			// If visible attributes are set, we'll only reveal those.
391 6
			foreach ( $this->visible as $key ) {
392 6
				$attributes[ $key ] = $this->get_attribute( $key );
393 4
			}
394 10
		} elseif ( $this->hidden ) {
395
			// If hidden attributes are set, we'll grab everything and hide those.
396 3
			foreach ( $this->get_attribute_keys() as $key ) {
397 3
				if ( ! in_array( $key, $this->hidden ) ) {
398 3
					$attributes[ $key ] = $this->get_attribute( $key );
399 2
				}
400 2
			}
401 2
		} else {
402
			// If nothing is hidden/visible, we'll grab and reveal everything.
403 3
			foreach ( $this->get_attribute_keys() as $key ) {
404 3
				$attributes[ $key ] = $this->get_attribute( $key );
405 2
			}
406
		}
407
408 4
		return array_map( function ( $attribute ) {
409 12
			if ( $attribute instanceof Serializes ) {
410 3
				return $attribute->serialize();
411
			}
412
413 12
			return $attribute;
414 12
		}, $attributes );
415
	}
416
417
	/**
418
	 * Syncs the current attributes to the model's original.
419
	 *
420
	 * @return $this
421
	 */
422 96
	public function sync_original() {
423 96
		$this->original = $this->attributes;
424
425 96
		if ( $this->attributes[ self::OBJECT_KEY ] ) {
426 9
			$this->original[ self::OBJECT_KEY ] = clone $this->attributes[ self::OBJECT_KEY ];
427 6
		}
428
429 96
		foreach ( $this->original[ self::TABLE_KEY ] as $key => $item ) {
430 9
			if ( is_object( $item ) ) {
431 9
				$this->original[ $key ] = clone $item;
432 6
			}
433 64
		}
434
435 96
		return $this;
436
	}
437
438
	/**
439
	 * Checks if a given attribute is mass-fillable.
440
	 *
441
	 * Returns true if the attribute can be filled, false if it can't.
442
	 *
443
	 * @param string $name
444
	 *
445
	 * @return bool
446
	 */
447 96
	private function is_fillable( $name ) {
448
		// If this model isn't guarded, everything is fillable.
449 96
		if ( ! $this->is_guarded ) {
450 96
			return true;
451
		}
452
453
		// If it's in the fillable array, then it's fillable.
454 18
		if ( in_array( $name, $this->fillable ) ) {
455 12
			return true;
456
		}
457
458
		// If it's explicitly guarded, then it's not fillable.
459 9
		if ( in_array( $name, $this->guarded ) ) {
460 9
			return false;
461
		}
462
463
		// If fillable hasn't been defined, then everything else fillable.
464
		return ! $this->fillable;
465
	}
466
467
	/**
468
	 * Overrides the current WordPress object with a provided one.
469
	 *
470
	 * Resets the post's default values and stores it in the attributes.
471
	 *
472
	 * @param WP_Post|WP_Term|null $value
473
	 *
474
	 * @return $this
475
	 */
476 27
	private function override_wp_object( $value ) {
477 27
		if ( is_object( $value ) ) {
478 27
			$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $value );
479 18
		} else {
480
			$this->attributes[ self::OBJECT_KEY ] = null;
481
482
			if ( $this->uses_wp_object() ) {
483
				$this->create_wp_object();
484
			}
485
		}
486
487 27
		return $this;
488
	}
489
490
	/**
491
	 * Overrides the current table attributes array with a provided one.
492
	 *
493
	 * @param array $value
494
	 *
495
	 * @return $this
496
	 */
497 6
	private function override_table( array $value ) {
498 6
		$this->attributes[ self::TABLE_KEY ] = $value;
499
500 6
		return $this;
501
	}
502
503
	/**
504
	 * Create and set with a new blank post.
505
	 *
506
	 * Creates a new WP_Post object, assigns it the default attributes,
507
	 * and stores it in the attributes.
508
	 *
509
	 * @throws LogicException
510
	 */
511 60
	private function create_wp_object() {
512 40
		switch ( true ) {
513 60
			case $this instanceof UsesWordPressPost:
514 60
				$object = new WP_Post( (object) array() );
515 60
				break;
516
			case $this instanceof UsesWordPressTerm:
517
				$object = new WP_Term( (object) array() );
518
				break;
519
			default:
520
				throw new LogicException;
521
				break;
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 60
		$this->attributes[ self::OBJECT_KEY ] = $this->set_wp_object_constants( $object );
525 60
	}
526
527
	/**
528
	 * Enforces values on the post that can't change.
529
	 *
530
	 * Primarily, this is used to make sure the post_type always maps
531
	 * to the model's "$type" property, but this can all be overridden
532
	 * by the developer to enforce other values in the model.
533
	 *
534
	 * @param object $object
535
	 *
536
	 * @return object
537
	 */
538 60
	protected function set_wp_object_constants( $object ) {
539 60
		if ( $this instanceof UsesWordPressPost ) {
540 60
			$object->post_type = static::get_post_type();
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 40
		}
542
543 60
		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 60
		return $object;
548
	}
549
550
	/**
551
	 * Magic __get method.
552
	 *
553
	 * Passes the name and value to get_attribute, which is where the magic happens.
554
	 *
555
	 * @param string $name
556
	 *
557
	 * @return mixed
558
	 */
559 24
	public function __get( $name ) {
560 24
		return $this->get_attribute( $name );
561
	}
562
563
	/**
564
	 * Retrieves the model attribute.
565
	 *
566
	 * @param string $name
567
	 *
568
	 * @return mixed
569
	 *
570
	 * @throws PropertyDoesNotExistException If property isn't found.
571
	 */
572 51
	public function get_attribute( $name ) {
573 51
		if ( $method = $this->has_map_method( $name ) ) {
574 24
			return $this->attributes[ self::OBJECT_KEY ]->{$this->{$method}()};
575
		}
576
577 39
		if ( $method = $this->has_compute_method( $name ) ) {
578 12
			return $this->{$method}();
579
		}
580
581 36
		if ( isset( $this->attributes[ self::TABLE_KEY ][ $name ] ) ) {
582 33
			return $this->attributes[ self::TABLE_KEY ][ $name ];
583
		}
584
585 3
		if ( isset( $this->defaults[ $name ] ) ) {
586
			return $this->defaults[ $name ];
587
		}
588
589 3
		return null;
590
	}
591
592
	/**
593
	 * Retrieve the model's original attribute value.
594
	 *
595
	 * @param string $name
596
	 *
597
	 * @return mixed
598
	 *
599
	 * @throws PropertyDoesNotExistException If property isn't found.
600
	 */
601 6
	public function get_original_attribute( $name ) {
602 6
		$original_attributes = $this->original;
603
604 6
		if ( ! is_object( $original_attributes[ static::OBJECT_KEY ] ) ) {
605
			unset( $original_attributes[ static::OBJECT_KEY ] );
606
		}
607
608 6
		$original = new static( $original_attributes );
609
610 6
		return $original->get_attribute( $name );
611
	}
612
613
	/**
614
	 * Fetches the Model's primary ID, depending on the model
615
	 * implementation.
616
	 *
617
	 * @return int
618
	 *
619
	 * @throws LogicException
620
	 */
621
	public function get_primary_id() {
622
		if ( $this instanceof UsesWordPressPost ) {
623
			return $this->get_underlying_wp_object()->ID;
624
		}
625
626
		if ( $this instanceof UsesWordPressTerm ) {
627
			return $this->get_underlying_wp_object()->term_id;
628
		}
629
630
		if ( $this instanceof UsesCustomTable ) {
631
			return $this->get_attribute( $this->get_primary_key() );
0 ignored issues
show
Bug introduced by
The method get_primary_key() 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...
632
		}
633
634
		// Model w/o wp_object not yet supported.
635
		throw new LogicException;
636
	}
637
638
	/**
639
	 * Checks whether the attribute has a map method.
640
	 *
641
	 * This is used to determine whether the attribute maps to a
642
	 * property on the underlying WP_Post object. Returns the
643
	 * method if one exists, returns false if it doesn't.
644
	 *
645
	 * @param string $name
646
	 *
647
	 * @return false|string
648
	 */
649 96
	protected function has_map_method( $name ) {
650 96
		if ( method_exists( $this, $method = "map_{$name}" ) ) {
651 60
			return $method;
652
		}
653
654 96
		return false;
655
	}
656
657
	/**
658
	 * Checks whether the attribute has a compute method.
659
	 *
660
	 * This is used to determine if the attribute should be computed
661
	 * from other attributes.
662
	 *
663
	 * @param string $name
664
	 *
665
	 * @return false|string
666
	 */
667 51
	protected function has_compute_method( $name ) {
668 51
		if ( method_exists( $this, $method = "compute_{$name}" ) ) {
669 21
			return $method;
670
		}
671
672 48
		return false;
673
	}
674
675
	/**
676
	 * Clears all the current attributes from the model.
677
	 *
678
	 * This does not touch the model's original attributes, and will
679
	 * only clear fillable attributes, unless the model is unguarded.
680
	 *
681
	 * @throws Exception
682
	 * @return $this
683
	 */
684 96
	public function clear() {
685 96
		$keys = array_merge(
686 96
			$this->get_table_keys(),
687 96
			$this->get_wp_object_keys()
688 64
		);
689
690 96
		foreach ( $keys as $key ) {
691
			try {
692 96
				$this->set_attribute( $key, null );
693 65
			} catch ( Exception $e ) {
694
				// We won't clear out guarded attributes.
695 3
				if ( ! ( $e instanceof GuardedPropertyException ) ) {
696
					throw $e;
697
				}
698
			}
699 64
		}
700
701 96
		return $this;
702
	}
703
704
	/**
705
	 * Unguards the model.
706
	 *
707
	 * Sets the model to be unguarded, allowing the filling of
708
	 * guarded attributes.
709
	 */
710 96
	public function unguard() {
711 96
		$this->is_guarded = false;
712 96
	}
713
714
	/**
715
	 * Reguards the model.
716
	 *
717
	 * Sets the model to be guarded, preventing filling of
718
	 * guarded attributes.
719
	 */
720 96
	public function reguard() {
721 96
		$this->is_guarded = true;
722 96
	}
723
724
	/**
725
	 * Retrieves all the compute methods on the model.
726
	 *
727
	 * @return array
728
	 */
729 9
	protected function get_compute_methods() {
730 9
		$methods = get_class_methods( get_called_class() );
731 3
		$methods = array_filter( $methods, function ( $method ) {
732 9
			return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
733 9
		} );
734 9
		$methods = array_map( function ( $method ) {
735 6
			return substr( $method, strlen( 'compute_' ) );
736 9
		}, $methods );
737
738 9
		return $methods;
739
	}
740
741
	/**
742
	 * Sets up the memo array for the creating model.
743
	 */
744 96
	private function maybe_boot() {
745 96
		if ( ! isset( self::$memo[ get_called_class() ] ) ) {
746 9
			self::$memo[ get_called_class() ] = array();
747 6
		}
748 96
	}
749
750
	/**
751
	 * Whether this Model uses an underlying WordPress object.
752
	 *
753
	 * @return bool
754
	 */
755 96
	protected function uses_wp_object() {
756 96
		return $this instanceof UsesWordPressPost ||
757 96
			$this instanceof UsesWordPressTerm;
758
	}
759
}
760