PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.11.0
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.11.0
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 All 281 releases
surecart / app / src / Models / DatabaseModel.php
DatabaseModel.php
1,180 lines 23.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SureCart\Models;
4
5 use ArrayAccess;
6 use JsonSerializable;
7 use SureCart\Concerns\Arrayable;
8 use SureCartVendors\PluginEver\QueryBuilder\Query;
9
10 /**
11 * Model class
12 */
13 abstract class DatabaseModel implements ArrayAccess, JsonSerializable, Arrayable, ModelInterface {
14 /**
15 * Keeps track of booted models
16 *
17 * @var array
18 */
19 protected static $booted = [];
20
21 /**
22 * Keeps track of model events
23 *
24 * @var array
25 */
26 protected static $events = [];
27
28 /**
29 * Stores model attributes
30 *
31 * @var array
32 */
33 protected $attributes = [];
34
35 /**
36 * Default attributes.
37 *
38 * @var array
39 */
40 protected $defaults = [];
41
42 /**
43 * Original attributes for dirty handling
44 *
45 * @var array
46 */
47 protected $original = [];
48
49 /**
50 * Rest API endpoint
51 *
52 * @var string
53 */
54 protected $endpoint = '';
55
56
57 /**
58 * Object name
59 *
60 * @var string
61 */
62 protected $object_name = '';
63
64 /**
65 * Query arguments
66 *
67 * @var array
68 */
69 protected $query = [];
70
71 /**
72 * Stores model relations
73 *
74 * @var array
75 */
76 protected $relations = [];
77
78 /**
79 * Fillable model items
80 *
81 * @var array
82 */
83 protected $fillable = [ '*' ];
84
85 /**
86 * Guarded model items
87 *
88 * @var array
89 */
90 protected $guarded = [];
91
92 /**
93 * Default collection limit
94 *
95 * @var integer
96 */
97 protected $limit = 20;
98
99
100 /**
101 * Default collection offset.
102 *
103 * @var integer
104 */
105 protected $offset = 0;
106
107 /**
108 * The default transient cache time
109 *
110 * @var integer
111 */
112 protected $transient_cache_time = 5 * MINUTE_IN_SECONDS;
113
114 /**
115 * Holds the db class.
116 *
117 * @var \SureCartVendors\PluginEver\QueryBuilder\Query
118 */
119 protected $db;
120
121 /**
122 * The table name for the model.
123 *
124 * @var string
125 */
126 protected $table_name;
127
128 /**
129 * Total items found.
130 *
131 * @var integer
132 */
133 protected $total;
134
135 /**
136 * Model constructor
137 *
138 * @param array $attributes Optional attributes.
139 */
140 public function __construct( $attributes = [] ) {
141 // if we have string here, assume it's the id.
142 if ( is_string( $attributes ) ) {
143 $attributes = [ 'id' => $attributes ];
144 }
145
146 $this->bootModel();
147 $this->syncOriginal();
148 $this->fill( $attributes );
149 }
150
151 /**
152 * Get the query.
153 *
154 * @return \SureCartVendors\PluginEver\QueryBuilder\Query
155 */
156 public function getQuery() {
157 if ( ! $this->query ) {
158 $this->query = $this->getDBInstance();
159 }
160 return $this->query;
161 }
162
163 /**
164 * Get the db instance.
165 *
166 * @return \SureCartVendors\PluginEver\QueryBuilder\Query
167 */
168 public function getDBInstance() {
169 return Query::init( $this->object_name )->select( '*' )->from( $this->table_name );
170 }
171
172 /**
173 * Sync original attributes in case of dirty needs
174 *
175 * @return Model
176 */
177 public function syncOriginal() {
178 $this->original = $this->attributes;
179 return $this;
180 }
181
182 /**
183 * Run boot method on model
184 *
185 * @return void
186 */
187 public function bootModel() {
188 $called_class = static::getCalledClassName();
189
190 if ( ! isset( static::$booted[ $called_class ] ) ) {
191 static::$booted[ $called_class ] = true;
192 static::boot();
193 }
194 }
195
196 /**
197 * Get the called class name
198 *
199 * @return string
200 */
201 public static function getCalledClassName() {
202 $class = get_called_class();
203 $class = explode( '\\', $class );
204 end( $class );
205 $last = key( $class );
206 return strtolower( $class[ $last ] );
207 }
208
209 /**
210 * Model boot method
211 *
212 * @return void
213 */
214 public static function boot() {
215 // Note: Don't remove this method.
216 }
217
218 /**
219 * Does it have the attribute
220 *
221 * @param string $key Attribute key.
222 *
223 * @return boolean
224 */
225 public function hasAttribute( $key ) {
226 return array_key_exists( $key, $this->attributes );
227 }
228
229 /**
230 * Register a model event
231 *
232 * @param string $event Event name.
233 * @param function $callback Callback function.
234 *
235 * @return void
236 */
237 public static function registerModelEvent( $event, $callback ) {
238 $called_class = static::getCalledClassName();
239 static::$events[ "model.{$called_class}.{$event}" ] = $callback;
240 }
241
242 /**
243 * Model has been retrieved
244 *
245 * @param function $callback Callback method.
246 *
247 * @return void
248 */
249 public static function retrieved( $callback ) {
250 static::registerModelEvent( __FUNCTION__, $callback );
251 }
252
253 /**
254 * Model Saving (Before Save)
255 *
256 * @param function $callback Callback method.
257 *
258 * @return void
259 */
260 public static function saving( $callback ) {
261 static::registerModelEvent( __FUNCTION__, $callback );
262 }
263
264 /**
265 * Model Savied (After Save)
266 *
267 * @param function $callback Callback method.
268 *
269 * @return void
270 */
271 public static function saved( $callback ) {
272 static::registerModelEvent( __FUNCTION__, $callback );
273 }
274
275 /**
276 * Model Creating (Before Create)
277 *
278 * @param function $callback Callback method.
279 *
280 * @return void
281 */
282 public static function creating( $callback ) {
283 static::registerModelEvent( __FUNCTION__, $callback );
284 }
285
286 /**
287 * Model Created (After Create)
288 *
289 * @param function $callback Callback method.
290 *
291 * @return void
292 */
293 public static function created( $callback ) {
294 static::registerModelEvent( __FUNCTION__, $callback );
295 }
296
297 /**
298 * Model Updating (Before Updating)
299 *
300 * @param function $callback Callback method.
301 *
302 * @return void
303 */
304 public static function updating( $callback ) {
305 static::registerModelEvent( __FUNCTION__, $callback );
306 }
307
308 /**
309 * Model Updating (After Updating)
310 *
311 * @param function $callback Callback method.
312 *
313 * @return void
314 */
315 public static function updated( $callback ) {
316 static::registerModelEvent( __FUNCTION__, $callback );
317 }
318
319 /**
320 * Model Deleting (Before Deleting)
321 *
322 * @param function $callback Callback method.
323 *
324 * @return void
325 */
326 public static function deleting( $callback ) {
327 static::registerModelEvent( __FUNCTION__, $callback );
328 }
329
330 /**
331 * Model Deleted (After Deleted)
332 *
333 * @param function $callback Callback method.
334 *
335 * @return void
336 */
337 public static function deleted( $callback ) {
338 static::registerModelEvent( __FUNCTION__, $callback );
339 }
340
341 /**
342 * Fires the model event.
343 *
344 * @param string $event Event name.
345 *
346 * @return mixed
347 */
348 public function fireModelEvent( $event ) {
349 $called_class = static::getCalledClassName();
350 $event_name = "model.{$called_class}.{$event}";
351
352 // fire global event.
353 \do_action( "surecart/models/{$called_class}/{$event}", $this );
354
355 if ( isset( static::$events[ $event_name ] ) ) {
356 return call_user_func( static::$events[ $event_name ], $this );
357 }
358 }
359
360 /**
361 * Sets attributes in model
362 *
363 * @param array $attributes Attributes to fill.
364 *
365 * @return Model
366 */
367 public function fill( $attributes ) {
368 return $this->setAttributes( $attributes );
369 }
370
371 /**
372 * Reset attributes to blank.
373 *
374 * @return $this
375 */
376 public function resetAttributes() {
377 $this->attributes = [];
378 return $this;
379 }
380
381 /**
382 * Set model attributes
383 *
384 * @param array $attributes Attributes to add.
385 * @param boolean $is_guarded Use guarded.
386 *
387 * @return Model
388 */
389 public function setAttributes( $attributes, $is_guarded = true ) {
390 if ( empty( $attributes ) ) {
391 return $this;
392 }
393
394 foreach ( $attributes as $key => $value ) {
395 // remove api attributes.
396 if ( in_array( $key, [ '_locale', 'rest_route' ], true ) ) {
397 continue;
398 }
399
400 // set attribute.
401 if ( ! $is_guarded ) {
402 $this->setAttribute( $key, maybe_unserialize( maybe_unserialize( $value ) ) );
403 } elseif ( $this->isFillable( $key ) ) {
404 $this->setAttribute( $key, maybe_unserialize( maybe_unserialize( $value ) ) );
405 }
406 }
407
408 return apply_filters( "surecart/{$this->object_name}/set_attributes", $this );
409 }
410
411 /**
412 * Make sure ID is always int.
413 *
414 * @param mixed $value Id.
415 *
416 * @return void
417 */
418 public function setIdAttribute( $value ) {
419 $this->attributes['id'] = (int) $value;
420 }
421
422 /**
423 * Sets an attribute
424 * Optionally calls a mutator based on set{Attribute}Attribute
425 *
426 * @param string $key Attribute key.
427 * @param mixed $value Attribute value.
428 *
429 * @return mixed|void
430 */
431 public function setAttribute( $key, $value ) {
432 $setter = $this->getMutator( $key, 'set' );
433
434 if ( $setter ) {
435 return $this->{$setter}( $value );
436 } else {
437 $this->attributes[ $key ] = $value;
438 }
439 }
440
441 /**
442 * Calls a mutator based on set{Attribute}Attribute
443 *
444 * @param string $key Attribute key.
445 * @param mixed $type 'get' or 'set'.
446 *
447 * @return string|false
448 */
449 public function getMutator( $key, $type ) {
450 $key = ucwords( str_replace( [ '-', '_' ], ' ', $key ) );
451
452 $method = $type . str_replace( ' ', '', $key ) . 'Attribute';
453
454 if ( method_exists( $this, $method ) ) {
455 return $method;
456 }
457
458 return false;
459 }
460
461 /**
462 * Is the attribute fillable?
463 *
464 * @param string $key Attribute name.
465 *
466 * @return boolean
467 */
468 public function isFillable( $key ) {
469 $fillable = $this->getFillable();
470
471 if ( in_array( $key, $fillable, true ) ) {
472 return true;
473 }
474
475 if ( $this->isGuarded( $key ) ) {
476 return false;
477 }
478
479 return ! empty( $fillable ) && '*' === $fillable[0];
480 }
481
482 /**
483 * Is the key guarded
484 *
485 * @param string $key Name of the attribute.
486 *
487 * @return boolean
488 */
489 public function isGuarded( $key ) {
490 $guarded = $this->getGuarded();
491 return in_array( $key, $guarded, true ) || [ '*' ] === $guarded;
492 }
493
494 /**
495 * Get fillable items
496 *
497 * @return array
498 */
499 public function getFillable() {
500 return $this->fillable;
501 }
502
503 /**
504 * Get guarded items
505 *
506 * @return array
507 */
508 public function getGuarded() {
509 return $this->guarded;
510 }
511 /**
512 * Build query query
513 *
514 * @param array $query Arguments.
515 *
516 * @return Model
517 */
518 protected function with( $query = [] ) {
519 $this->query['expand'] = (array) array_merge( $query, $this->query['expand'] ?? [] );
520 return $this;
521 }
522
523 /**
524 * Set the request mode (test or live).
525 *
526 * @param 'test'|'live' $mode Request mode.
527 */
528 public function setMode( $mode ) {
529 $this->mode = $mode;
530 return $this;
531 }
532
533 /**
534 * Get the current mode.
535 */
536 public function getMode() {
537 return $this->mode;
538 }
539
540 /**
541 * Set the pagination args.
542 *
543 * @param array $args Pagination args.
544 * @return $this
545 */
546 protected function setPagination( $args ) {
547 $args = wp_parse_args(
548 $args,
549 [
550 'page' => 1,
551 'per_page' => 20,
552 ]
553 );
554
555 $this->query = $this->getQuery();
556
557 $size = (int) $args['per_page'];
558 $limit = (int) $size;
559 $offset = (int) $size * ( ( (int) $args['page'] ) - 1 );
560
561 $this->query->limit( $offset, $limit );
562
563 return $this;
564 }
565
566 /**
567 * Fetch a list of items
568 *
569 * @return array|\WP_Error;
570 */
571 protected function get() {
572 $this->query = $this->getQuery();
573 $result = $this->query->get();
574 $models = [];
575 foreach ( $result as $data ) {
576 $models[] = new static( $data );
577 }
578 return $models;
579 }
580
581 /**
582 * Paginate results
583 *
584 * @param array $args Pagination args.
585 * @return mixed
586 */
587 protected function paginate( $args = [] ) {
588 $this->setPagination( $args );
589 return $this->get();
590 }
591
592 /**
593 * Get the first item in the query
594 */
595 public function first() {
596 $this->query = $this->getQuery();
597 $result = $this->query->one();
598 return $result ? new static( $result ) : null;
599 }
600
601 /**
602 * Find a specific model with and id
603 *
604 * @param string $id Id of the model.
605 *
606 * @return $this
607 */
608 protected function find( $id = '' ) {
609 if ( $this->fireModelEvent( 'finding' ) === false ) {
610 return false;
611 }
612
613 $attributes = $this->getQuery()->find( $id, 'id' );
614 if ( ! $attributes ) {
615 return new \WP_Error( 'not_found', 'This does not exist.' );
616 }
617
618 $this->fireModelEvent( 'found' );
619 $this->syncOriginal();
620 $this->fill( $attributes );
621
622 return $this;
623 }
624
625 /**
626 * Is the response an Error?
627 *
628 * @param Array|\WP_Error|\WP_REST_Response $response Response from request.
629 *
630 * @return boolean
631 */
632 public function isError( $response ) {
633 return is_wp_error( $response ) || ( $response instanceof \WP_REST_Response && ! in_array( $response->get_status(), [ 200, 201 ], true ) );
634 }
635
636 /**
637 * Get fresh instance from DB.
638 *
639 * @return this
640 */
641 protected function fresh() {
642 if ( ! $this->attributes['id'] ) {
643 return $this;
644 }
645 return $this->find( $this->attributes['id'] );
646 }
647
648 /**
649 * Get fresh instance from DB.
650 *
651 * @return this
652 */
653 protected function refresh() {
654 if ( ! $this->attributes['id'] ) {
655 return $this;
656 }
657
658 $attributes = $this->fresh();
659
660 if ( is_wp_error( $attributes ) ) {
661 return $attributes;
662 }
663
664 $this->syncOriginal();
665 $this->fill( $attributes );
666
667 return $this;
668 }
669
670 /**
671 * Save model
672 *
673 * @return $this|false
674 */
675 protected function save() {
676 if ( $this->fireModelEvent( 'saving' ) === false ) {
677 return false;
678 }
679
680 // update or create.
681 if ( $this->id ) {
682 $saved = $this->isDirty() ? $this->update() : true;
683 } else {
684 $saved = $this->create();
685 }
686
687 if ( is_wp_error( $saved ) ) {
688 return $saved;
689 }
690
691 $this->fireModelEvent( 'saved' );
692
693 $this->syncOriginal();
694
695 return $saved;
696 }
697
698 /**
699 * Create a new model
700 *
701 * @param array $attributes Attributes to create.
702 *
703 * @return $this|false
704 */
705 protected function create( $attributes = [] ) {
706 if ( $this->fireModelEvent( 'creating' ) === false ) {
707 return false;
708 }
709
710 if ( $attributes ) {
711 $this->syncOriginal();
712 $this->fill( $attributes );
713 }
714
715 $id = $this->getQuery()->insert(
716 array_merge(
717 [ 'created_at' => current_time( 'mysql' ) ],
718 array_map(
719 function ( $item ) {
720 return maybe_serialize( $item );
721 },
722 $this->attributes
723 )
724 )
725 );
726 if ( ! $id ) {
727 return new \WP_Error( 'could_not_create', 'Could not create.' );
728 }
729
730 // bail if error.
731 $created = $this->getQuery()->find( $id, 'id' );
732 if ( ! $created ) {
733 return new \WP_Error( 'could_not_create', 'Could not create.' );
734 }
735
736 // reset.
737 $this->resetAttributes();
738
739 // fill.
740 $this->fill( $created ?? [] );
741
742 // fire event.
743 $this->fireModelEvent( 'created' );
744
745 return $this;
746 }
747
748 /**
749 * Update the model.
750 *
751 * @return $this|false
752 */
753 protected function update( $attributes = [] ) {
754 if ( $this->fireModelEvent( 'updating' ) === false ) {
755 return false;
756 }
757
758 if ( $attributes ) {
759 $this->syncOriginal();
760 $this->fill( $attributes );
761 }
762
763 $attributes = $this->attributes;
764 unset( $attributes['id'] );
765
766 $item = $this->first();
767 if ( is_wp_error( $item ) ) {
768 return $item;
769 }
770
771 $update = $this->getQuery()->where( 'id', $item->id )->update(
772 array_merge(
773 [ 'updated_at' => current_time( 'mysql' ) ],
774 array_map(
775 function ( $item ) {
776 return maybe_serialize( $item );
777 },
778 $attributes
779 )
780 )
781 );
782 if ( false === $update ) {
783 return new \WP_Error( 'could_not_update', 'Could not update.' );
784 }
785
786 // bail if error.
787 $updated = $this->getDBInstance()->find( $item->id, 'id' );
788 if ( ! $updated ) {
789 return new \WP_Error( 'could_not_update', 'Could not update. id:' . $item->id );
790 }
791
792 $this->resetAttributes();
793
794 $this->fill( $updated );
795
796 $this->fireModelEvent( 'updated' );
797
798 return $this;
799 }
800
801 /**
802 * Delete the model.
803 *
804 * @return $this|false
805 */
806 protected function delete( $id = 0 ) {
807 $id = $id ? $id : $this->id;
808
809 if ( $this->fireModelEvent( 'deleting' ) === false ) {
810 return false;
811 }
812
813 $delete = $this->getQuery();
814
815 // deleting a single.
816 if ( $id ) {
817 $delete = $delete->where( 'ID', $id );
818 }
819
820 // delete.
821 $deleted = $delete->delete();
822
823 if ( false === $deleted ) {
824 return new \WP_Error( 'could_not_delete', 'Could not delete.' );
825 }
826
827 $this->fireModelEvent( 'deleted' );
828
829 return $deleted;
830 }
831
832 /**
833 * Delete all items.
834 *
835 * @return $this|false
836 */
837 protected function deleteAll() {
838 $delete = $this->getQuery();
839 $deleted = $delete->delete();
840 if ( false === $deleted ) {
841 return new \WP_Error( 'could_not_delete', 'Could not delete.' );
842 }
843 return $deleted;
844 }
845
846 /**
847 * Set a model relation.
848 *
849 * @prop string $attribute Attribute name.
850 * @prop string $value Value to set.
851 * @prop string $model Model name.
852 * @return void
853 */
854 public function setRelation( $attribute, $value, $model ) {
855 if ( $value ) {
856 $this->attributes[ $attribute ] = is_string( $value ) ? $value : new $model( $value );
857 }
858 }
859
860 /**
861 * Get a relation id.
862 *
863 * @prop string $attribute Attribute name.
864 * @prop string $value Value to set.
865 * @prop string $model Model name.
866 * @return string|null;
867 */
868 public function getRelationId( $attribute ) {
869 $value = $this->attributes[ $attribute ] ?? null;
870 return ! empty( $value['id'] ) ? $value['id'] : $value;
871 }
872
873 /**
874 * Set a model collection.
875 *
876 * @prop string $attribute Attribute name.
877 * @prop string $value Value to set.
878 * @prop string $model Model name.
879 * @return void
880 */
881 public function setCollection( $attribute, $collection, $model ) {
882 $models = [];
883 if ( ! empty( $collection->data ) && is_array( $collection->data ) ) {
884 foreach ( $collection->data as $attributes ) {
885 $models[] = new $model( $attributes );
886 }
887 $collection->data = $models;
888 }
889 $this->attributes[ $attribute ] = $collection;
890 }
891
892 /**
893 * Get the model attributes
894 *
895 * @return array
896 */
897 public function getAttributes() {
898 return json_decode( wp_json_encode( array_merge( $this->attributes, [ 'object' => $this->object_name ] ) ), true );
899 }
900
901 /**
902 * Get a specific attribute
903 *
904 * @param string $key Attribute name.
905 *
906 * @return mixed
907 */
908 public function getAttribute( $key ) {
909 $attribute = null;
910
911 if ( $this->hasAttribute( $key ) ) {
912 $attribute = $this->attributes[ $key ];
913 }
914
915 $getter = $this->getMutator( $key, 'get' );
916
917 if ( $getter ) {
918 return $this->{$getter}( $attribute );
919 } elseif ( ! is_null( $attribute ) ) {
920 return $attribute;
921 }
922 }
923
924 /**
925 * Serialize to json.
926 *
927 * @return Array
928 */
929 #[\ReturnTypeWillChange]
930 public function jsonSerialize() {
931 return $this->toArray();
932 }
933
934 /**
935 * Calls accessors during toArray.
936 *
937 * @return array
938 */
939 public function toArray() {
940 $attributes = $this->getAttributes();
941
942 // Check if any accessor is available and call it.
943 foreach ( get_class_methods( $this ) as $method ) {
944 if ( ! method_exists( get_class( $this ), $method ) ) {
945 continue;
946 }
947
948 if ( 'get' === substr( $method, 0, 3 ) && 'Attribute' === substr( $method, -9 ) ) {
949 $key = str_replace( [ 'get', 'Attribute' ], '', $method );
950 if ( $key ) {
951 $pieces = preg_split( '/(?=[A-Z])/', $key );
952 $pieces = array_map( 'strtolower', array_filter( $pieces ) );
953 $key = implode( '_', $pieces );
954 $value = array_key_exists( $key, $this->attributes ) ? $this->attributes[ $key ] : null;
955 $attributes[ $key ] = $this->{$method}( $value );
956 }
957 }
958 }
959
960 // Check if any attribute is a model and call toArray.
961 array_walk_recursive(
962 $attributes,
963 function ( &$value, $key ) {
964 if ( $value instanceof Model ) {
965 $value = $value->toArray();
966 }
967 }
968 );
969
970 return array_merge( $attributes, $this->relations );
971 }
972
973 /**
974 * Is the model dirty (has unsaved items)
975 *
976 * @param array $attributes Optionally pass attributes to check.
977 *
978 * @return boolean
979 */
980 public function isDirty( $attributes = null ) {
981 $dirty = $this->getDirty();
982
983 if ( is_null( $attributes ) ) {
984 return count( $dirty ) > 0;
985 }
986
987 if ( ! is_array( $attributes ) ) {
988 $attributes = func_get_args();
989 }
990
991 foreach ( $attributes as $attribute ) {
992 if ( array_key_exists( $attribute, $dirty ) ) {
993 return true;
994 }
995 }
996
997 return false;
998 }
999
1000 /**
1001 * Get dirty (unsaved) items
1002 *
1003 * @return array
1004 */
1005 public function getDirty() {
1006 $dirty = [];
1007
1008 foreach ( $this->attributes as $key => $value ) {
1009 if ( ! array_key_exists( $key, $this->original ) ) {
1010 $dirty[ $key ] = $value;
1011 } elseif ( $value !== $this->original[ $key ] &&
1012 ! $this->originalIsNumericallyEquivalent( $key ) ) {
1013 $dirty[ $key ] = $value;
1014 }
1015 }
1016
1017 return $dirty;
1018 }
1019
1020 /**
1021 * Determine if the new and old values for a given key are numerically equivalent.
1022 *
1023 * @param string $key Attribute name.
1024 * @return bool
1025 */
1026 protected function originalIsNumericallyEquivalent( $key ) {
1027 $current = $this->attributes[ $key ];
1028 $original = $this->original[ $key ];
1029 return is_numeric( $current ) && is_numeric( $original ) && strcmp( (string) $current, (string) $original ) === 0;
1030 }
1031
1032 /**
1033 * Get the original item
1034 *
1035 * @param string $key Name of the item.
1036 *
1037 * @return mixed
1038 */
1039 public function getOriginal( $key = null ) {
1040 if ( ! is_null( $key ) ) {
1041 return isset( $this->original[ $key ] ) ? $this->original[ $key ] : null;
1042 }
1043 return $this->original;
1044 }
1045
1046 /**
1047 * Get the attribute
1048 *
1049 * @param string $key Attribute name.
1050 *
1051 * @return mixed
1052 */
1053 public function __get( $key ) {
1054 return $this->getAttribute( $key );
1055 }
1056
1057 /**
1058 * Set the attribute
1059 *
1060 * @param string $key Attribute name.
1061 * @param mixed $value Value of attribute.
1062 *
1063 * @return void
1064 */
1065 public function __set( $key, $value ) {
1066 $this->setAttribute( $key, $value );
1067 }
1068
1069 /**
1070 * Determine if the given attribute exists.
1071 *
1072 * @param mixed $offset Name.
1073 * @return bool
1074 */
1075 #[\ReturnTypeWillChange]
1076 public function offsetExists( $offset ) {
1077 return ! is_null( $this->getAttribute( $offset ) );
1078 }
1079
1080 /**
1081 * Get the value for a given offset.
1082 *
1083 * @param mixed $offset Name.
1084 * @return mixed
1085 */
1086 #[\ReturnTypeWillChange]
1087 public function offsetGet( $offset ) {
1088 return $this->getAttribute( $offset );
1089 }
1090
1091 /**
1092 * Set the value for a given offset.
1093 *
1094 * @param mixed $offset Name.
1095 * @param mixed $value Value.
1096 * @return void
1097 */
1098 #[\ReturnTypeWillChange]
1099 public function offsetSet( $offset, $value ) {
1100 $this->setAttribute( $offset, $value );
1101 }
1102
1103 /**
1104 * Unset the value for a given offset.
1105 *
1106 * @param mixed $offset Name.
1107 * @return void
1108 */
1109 #[\ReturnTypeWillChange]
1110 public function offsetUnset( $offset ) {
1111 unset( $this->attributes[ $offset ], $this->relations[ $offset ] );
1112 }
1113
1114 /**
1115 * Determine if an attribute or relation exists on the model.
1116 *
1117 * @param string $key Name.
1118 * @return bool
1119 */
1120 public function __isset( $key ) {
1121 return $this->offsetExists( $key );
1122 }
1123
1124 /**
1125 * Unset an attribute on the model.
1126 *
1127 * @param string $key Name.
1128 * @return void
1129 */
1130 public function __unset( $key ) {
1131 $this->offsetUnset( $key );
1132 }
1133
1134 /**
1135 * Forward call to method
1136 *
1137 * @param string $method Method to call.
1138 * @param mixed $params Method params.
1139 */
1140 public function __call( $method, $params ) {
1141 // if we have the method.
1142 if ( method_exists( $this, $method ) ) {
1143 return call_user_func_array( [ $this, $method ], $params );
1144 }
1145 // if the query builder has a method, start building query.
1146 if ( method_exists( Query::class, $method ) ) {
1147 $result = $this->getQuery()->{$method}( ...$params );
1148 if ( in_array( $method, [ 'count' ] ) ) {
1149 return $result;
1150 }
1151 return $this;
1152 }
1153 }
1154
1155 /**
1156 * Static Facade Accessor
1157 *
1158 * @param string $method Method to call.
1159 * @param mixed $params Method params.
1160 *
1161 * @return mixed
1162 */
1163 public static function __callStatic( $method, $params ) {
1164 // if we have the method.
1165 if ( method_exists( static::class, $method ) ) {
1166 return call_user_func_array( [ new static(), $method ], $params );
1167 }
1168
1169 // if the query builder has a method, start building query.
1170 if ( method_exists( Query::class, $method ) ) {
1171 $item = new static();
1172 $result = $item->getQuery()->{$method}( ...$params );
1173 if ( in_array( $method, [ 'count' ] ) ) {
1174 return $result;
1175 }
1176 return $item;
1177 }
1178 }
1179 }
1180