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() { |
|
|
|
|
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 ) && |
|
|
|
|
330
|
9 |
|
! $this->has_compute_method( $key ) |
|
|
|
|
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() { |
|
|
|
|
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 ) ) { |
|
|
|
|
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() { |
|
|
|
|
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 ) ) { |
|
|
|
|
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; |
|
|
|
|
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(); |
|
|
|
|
541
|
40 |
|
} |
542
|
|
|
|
543
|
60 |
|
if ( $this instanceof UsesWordPressTerm ) { |
544
|
|
|
$object->taxonomy = static::get_taxonomy(); |
|
|
|
|
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() ); |
|
|
|
|
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
|
|
|
|
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.