PluginProbe
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 2.29.4
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v2.29.4
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,159 lines 22.6 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 } else {
404 if ( $this->isFillable( $key ) ) {
405 $this->setAttribute( $key, maybe_unserialize( maybe_unserialize( $value ) ) );
406 }
407 }
408 }
409
410 return apply_filters( "surecart/{$this->object_name}/set_attributes", $this );
411 }
412
413 /**
414 * Make sure ID is always int.
415 *
416 * @param mixed $value Id.
417 *
418 * @return void
419 */
420 public function setIdAttribute( $value ) {
421 $this->attributes['id'] = (int) $value;
422 }
423
424 /**
425 * Sets an attribute
426 * Optionally calls a mutator based on set{Attribute}Attribute
427 *
428 * @param string $key Attribute key.
429 * @param mixed $value Attribute value.
430 *
431 * @return mixed|void
432 */
433 public function setAttribute( $key, $value ) {
434 $setter = $this->getMutator( $key, 'set' );
435
436 if ( $setter ) {
437 return $this->{$setter}( $value );
438 } else {
439 $this->attributes[ $key ] = $value;
440 }
441 }
442
443 /**
444 * Calls a mutator based on set{Attribute}Attribute
445 *
446 * @param string $key Attribute key.
447 * @param mixed $type 'get' or 'set'.
448 *
449 * @return string|false
450 */
451 public function getMutator( $key, $type ) {
452 $key = ucwords( str_replace( [ '-', '_' ], ' ', $key ) );
453
454 $method = $type . str_replace( ' ', '', $key ) . 'Attribute';
455
456 if ( method_exists( $this, $method ) ) {
457 return $method;
458 }
459
460 return false;
461 }
462
463 /**
464 * Is the attribute fillable?
465 *
466 * @param string $key Attribute name.
467 *
468 * @return boolean
469 */
470 public function isFillable( $key ) {
471 $fillable = $this->getFillable();
472
473 if ( in_array( $key, $fillable, true ) ) {
474 return true;
475 }
476
477 if ( $this->isGuarded( $key ) ) {
478 return false;
479 }
480
481 return ! empty( $fillable ) && '*' === $fillable[0];
482 }
483
484 /**
485 * Is the key guarded
486 *
487 * @param string $key Name of the attribute.
488 *
489 * @return boolean
490 */
491 public function isGuarded( $key ) {
492 $guarded = $this->getGuarded();
493 return in_array( $key, $guarded, true ) || [ '*' ] === $guarded;
494 }
495
496 /**
497 * Get fillable items
498 *
499 * @return array
500 */
501 public function getFillable() {
502 return $this->fillable;
503 }
504
505 /**
506 * Get guarded items
507 *
508 * @return array
509 */
510 public function getGuarded() {
511 return $this->guarded;
512 }
513 /**
514 * Build query query
515 *
516 * @param array $query Arguments.
517 *
518 * @return Model
519 */
520 protected function with( $query = [] ) {
521 $this->query['expand'] = (array) array_merge( $query, $this->query['expand'] ?? [] );
522 return $this;
523 }
524
525 /**
526 * Set the request mode (test or live).
527 *
528 * @param 'test'|'live' $mode Request mode.
529 */
530 public function setMode( $mode ) {
531 $this->mode = $mode;
532 return $this;
533 }
534
535 /**
536 * Get the current mode.
537 */
538 public function getMode() {
539 return $this->mode;
540 }
541
542 /**
543 * Set the pagination args.
544 *
545 * @param array $args Pagination args.
546 * @return $this
547 */
548 protected function setPagination( $args ) {
549 $args = wp_parse_args(
550 $args,
551 [
552 'page' => 1,
553 'per_page' => 20,
554 ]
555 );
556
557 $this->query = $this->getQuery();
558
559 $size = (int) $args['per_page'];
560 $limit = (int) $size;
561 $offset = (int) $size * ( ( (int) $args['page'] ) - 1 );
562
563 $this->query->limit( $offset, $limit );
564
565 return $this;
566 }
567
568 /**
569 * Fetch a list of items
570 *
571 * @return array|\WP_Error;
572 */
573 protected function get() {
574 $this->query = $this->getQuery();
575 $result = $this->query->get();
576 $models = [];
577 foreach ( $result as $data ) {
578 $models[] = new static( $data );
579 }
580 return $models;
581 }
582
583 /**
584 * Paginate results
585 *
586 * @param array $args Pagination args.
587 * @return mixed
588 */
589 protected function paginate( $args = [] ) {
590 $this->setPagination( $args );
591 return $this->get();
592 }
593
594 /**
595 * Get the first item in the query
596 */
597 public function first() {
598 $this->query = $this->getQuery();
599 $result = $this->query->one();
600 return $result ? new static( $result ) : null;
601 }
602
603 /**
604 * Find a specific model with and id
605 *
606 * @param string $id Id of the model.
607 *
608 * @return $this
609 */
610 protected function find( $id = '' ) {
611 if ( $this->fireModelEvent( 'finding' ) === false ) {
612 return false;
613 }
614
615 $attributes = $this->getQuery()->find( $id, 'id' );
616 if ( ! $attributes ) {
617 return new \WP_Error( 'not_found', 'This does not exist.' );
618 }
619
620 $this->fireModelEvent( 'found' );
621 $this->syncOriginal();
622 $this->fill( $attributes );
623
624 return $this;
625 }
626
627 /**
628 * Is the response an Error?
629 *
630 * @param Array|\WP_Error|\WP_REST_Response $response Response from request.
631 *
632 * @return boolean
633 */
634 public function isError( $response ) {
635 return is_wp_error( $response ) || ( $response instanceof \WP_REST_Response && ! in_array( $response->get_status(), [ 200, 201 ], true ) );
636 }
637
638 /**
639 * Get fresh instance from DB.
640 *
641 * @return this
642 */
643 protected function fresh() {
644 if ( ! $this->attributes['id'] ) {
645 return $this;
646 }
647 return $this->find( $this->attributes['id'] );
648 }
649
650 /**
651 * Get fresh instance from DB.
652 *
653 * @return this
654 */
655 protected function refresh() {
656 if ( ! $this->attributes['id'] ) {
657 return $this;
658 }
659
660 $attributes = $this->fresh();
661
662 if ( is_wp_error( $attributes ) ) {
663 return $attributes;
664 }
665
666 $this->syncOriginal();
667 $this->fill( $attributes );
668
669 return $this;
670 }
671
672 /**
673 * Save model
674 *
675 * @return $this|false
676 */
677 protected function save() {
678 if ( $this->fireModelEvent( 'saving' ) === false ) {
679 return false;
680 }
681
682 // update or create.
683 if ( $this->id ) {
684 $saved = $this->isDirty() ? $this->update() : true;
685 } else {
686 $saved = $this->create();
687 }
688
689 if ( is_wp_error( $saved ) ) {
690 return $saved;
691 }
692
693 $this->fireModelEvent( 'saved' );
694
695 $this->syncOriginal();
696
697 return $saved;
698 }
699
700 /**
701 * Create a new model
702 *
703 * @param array $attributes Attributes to create.
704 *
705 * @return $this|false
706 */
707 protected function create( $attributes = [] ) {
708 if ( $this->fireModelEvent( 'creating' ) === false ) {
709 return false;
710 }
711
712 if ( $attributes ) {
713 $this->syncOriginal();
714 $this->fill( $attributes );
715 }
716
717 $id = $this->getQuery()->insert(
718 array_merge(
719 [ 'created_at' => current_time( 'mysql' ) ],
720 array_map(
721 function( $item ) {
722 return maybe_serialize( $item );
723 },
724 $this->attributes
725 )
726 )
727 );
728 if ( ! $id ) {
729 return new \WP_Error( 'could_not_create', 'Could not create.' );
730 }
731
732 // bail if error.
733 $created = $this->getQuery()->find( $id, 'id' );
734 if ( ! $created ) {
735 return new \WP_Error( 'could_not_create', 'Could not create.' );
736 }
737
738 // reset.
739 $this->resetAttributes();
740
741 // fill.
742 $this->fill( $created ?? [] );
743
744 // fire event.
745 $this->fireModelEvent( 'created' );
746
747 return $this;
748 }
749
750 /**
751 * Update the model.
752 *
753 * @return $this|false
754 */
755 protected function update( $attributes = [] ) {
756 if ( $this->fireModelEvent( 'updating' ) === false ) {
757 return false;
758 }
759
760 if ( $attributes ) {
761 $this->syncOriginal();
762 $this->fill( $attributes );
763 }
764
765 $attributes = $this->attributes;
766 unset( $attributes['id'] );
767
768 $item = $this->first();
769 if ( is_wp_error( $item ) ) {
770 return $item;
771 }
772
773 $update = $this->getQuery()->where( 'id', $item->id )->update(
774 array_merge(
775 [ 'updated_at' => current_time( 'mysql' ) ],
776 array_map(
777 function( $item ) {
778 return maybe_serialize( $item );
779 },
780 $attributes
781 )
782 )
783 );
784 if ( false === $update ) {
785 return new \WP_Error( 'could_not_update', 'Could not update.' );
786 }
787
788 // bail if error.
789 $updated = $this->getDBInstance()->find( $item->id, 'id' );
790 if ( ! $updated ) {
791 return new \WP_Error( 'could_not_update', 'Could not update. id:' . $item->id );
792 }
793
794 $this->resetAttributes();
795
796 $this->fill( $updated );
797
798 $this->fireModelEvent( 'updated' );
799
800 return $this;
801 }
802
803 /**
804 * Delete the model.
805 *
806 * @return $this|false
807 */
808 protected function delete( $id = 0 ) {
809 $id = $id ? $id : $this->id;
810
811 if ( $this->fireModelEvent( 'deleting' ) === false ) {
812 return false;
813 }
814
815 $deleted = $this->getQuery()->where( 'ID', $id )->delete();
816 if ( false === $deleted ) {
817 return new \WP_Error( 'could_not_delete', 'Could not delete.' );
818 }
819
820 $this->fireModelEvent( 'deleted' );
821
822 return $deleted;
823 }
824
825 /**
826 * Set a model relation.
827 *
828 * @prop string $attribute Attribute name.
829 * @prop string $value Value to set.
830 * @prop string $model Model name.
831 * @return void
832 */
833 public function setRelation( $attribute, $value, $model ) {
834 if ( $value ) {
835 $this->attributes[ $attribute ] = is_string( $value ) ? $value : new $model( $value );
836 }
837 }
838
839 /**
840 * Get a relation id.
841 *
842 * @prop string $attribute Attribute name.
843 * @prop string $value Value to set.
844 * @prop string $model Model name.
845 * @return string|null;
846 */
847 public function getRelationId( $attribute ) {
848 $value = $this->attributes[ $attribute ] ?? null;
849 return ! empty( $value['id'] ) ? $value['id'] : $value;
850 }
851
852 /**
853 * Set a model collection.
854 *
855 * @prop string $attribute Attribute name.
856 * @prop string $value Value to set.
857 * @prop string $model Model name.
858 * @return void
859 */
860 public function setCollection( $attribute, $collection, $model ) {
861 $models = [];
862 if ( ! empty( $collection->data ) && is_array( $collection->data ) ) {
863 foreach ( $collection->data as $attributes ) {
864 $models[] = new $model( $attributes );
865 }
866 $collection->data = $models;
867 }
868 $this->attributes[ $attribute ] = $collection;
869 }
870
871 /**
872 * Get the model attributes
873 *
874 * @return array
875 */
876 public function getAttributes() {
877 return json_decode( wp_json_encode( array_merge( $this->attributes, [ 'object' => $this->object_name ] ) ), true );
878 }
879
880 /**
881 * Get a specific attribute
882 *
883 * @param string $key Attribute name.
884 *
885 * @return mixed
886 */
887 public function getAttribute( $key ) {
888 $attribute = null;
889
890 if ( $this->hasAttribute( $key ) ) {
891 $attribute = $this->attributes[ $key ];
892 }
893
894 $getter = $this->getMutator( $key, 'get' );
895
896 if ( $getter ) {
897 return $this->{$getter}( $attribute );
898 } elseif ( ! is_null( $attribute ) ) {
899 return $attribute;
900 }
901 }
902
903 /**
904 * Serialize to json.
905 *
906 * @return Array
907 */
908 #[\ReturnTypeWillChange]
909 public function jsonSerialize() {
910 return $this->toArray();
911 }
912
913 /**
914 * Calls accessors during toArray.
915 *
916 * @return Array
917 */
918 public function toArray() {
919 $attributes = $this->getAttributes();
920
921 // Check if any accessor is available and call it.
922 foreach ( get_class_methods( $this ) as $method ) {
923 if ( method_exists( get_class(), $method ) ) {
924 continue;
925 }
926
927 if ( 'get' === substr( $method, 0, 3 ) && 'Attribute' === substr( $method, -9 ) ) {
928 $key = str_replace( [ 'get', 'Attribute' ], '', $method );
929 if ( $key ) {
930 $pieces = preg_split( '/(?=[A-Z])/', $key );
931 $pieces = array_map( 'strtolower', array_filter( $pieces ) );
932 $key = implode( '_', $pieces );
933 $value = array_key_exists( $key, $this->attributes ) ? $this->attributes[ $key ] : null;
934 $attributes[ $key ] = $this->{$method}( $value );
935 }
936 }
937 }
938
939 // Check if any attribute is a model and call toArray.
940 array_walk_recursive(
941 $attributes,
942 function ( &$value, $key ) {
943 if ( $value instanceof Model ) {
944 $value = $value->toArray();
945 }
946 }
947 );
948
949 return array_merge( $attributes, $this->relations );
950 }
951
952 /**
953 * Is the model dirty (has unsaved items)
954 *
955 * @param array $attributes Optionally pass attributes to check.
956 *
957 * @return boolean
958 */
959 public function isDirty( $attributes = null ) {
960 $dirty = $this->getDirty();
961
962 if ( is_null( $attributes ) ) {
963 return count( $dirty ) > 0;
964 }
965
966 if ( ! is_array( $attributes ) ) {
967 $attributes = func_get_args();
968 }
969
970 foreach ( $attributes as $attribute ) {
971 if ( array_key_exists( $attribute, $dirty ) ) {
972 return true;
973 }
974 }
975
976 return false;
977 }
978
979 /**
980 * Get dirty (unsaved) items
981 *
982 * @return array
983 */
984 public function getDirty() {
985 $dirty = [];
986
987 foreach ( $this->attributes as $key => $value ) {
988 if ( ! array_key_exists( $key, $this->original ) ) {
989 $dirty[ $key ] = $value;
990 } elseif ( $value !== $this->original[ $key ] &&
991 ! $this->originalIsNumericallyEquivalent( $key ) ) {
992 $dirty[ $key ] = $value;
993 }
994 }
995
996 return $dirty;
997 }
998
999 /**
1000 * Determine if the new and old values for a given key are numerically equivalent.
1001 *
1002 * @param string $key Attribute name.
1003 * @return bool
1004 */
1005 protected function originalIsNumericallyEquivalent( $key ) {
1006 $current = $this->attributes[ $key ];
1007 $original = $this->original[ $key ];
1008 return is_numeric( $current ) && is_numeric( $original ) && strcmp( (string) $current, (string) $original ) === 0;
1009 }
1010
1011 /**
1012 * Get the original item
1013 *
1014 * @param string $key Name of the item.
1015 *
1016 * @return mixed
1017 */
1018 public function getOriginal( $key = null ) {
1019 if ( ! is_null( $key ) ) {
1020 return isset( $this->original[ $key ] ) ? $this->original[ $key ] : null;
1021 }
1022 return $this->original;
1023 }
1024
1025 /**
1026 * Get the attribute
1027 *
1028 * @param string $key Attribute name.
1029 *
1030 * @return mixed
1031 */
1032 public function __get( $key ) {
1033 return $this->getAttribute( $key );
1034 }
1035
1036 /**
1037 * Set the attribute
1038 *
1039 * @param string $key Attribute name.
1040 * @param mixed $value Value of attribute.
1041 *
1042 * @return void
1043 */
1044 public function __set( $key, $value ) {
1045 $this->setAttribute( $key, $value );
1046 }
1047
1048 /**
1049 * Determine if the given attribute exists.
1050 *
1051 * @param mixed $offset Name.
1052 * @return bool
1053 */
1054 #[\ReturnTypeWillChange]
1055 public function offsetExists( $offset ) {
1056 return ! is_null( $this->getAttribute( $offset ) );
1057 }
1058
1059 /**
1060 * Get the value for a given offset.
1061 *
1062 * @param mixed $offset Name.
1063 * @return mixed
1064 */
1065 #[\ReturnTypeWillChange]
1066 public function offsetGet( $offset ) {
1067 return $this->getAttribute( $offset );
1068 }
1069
1070 /**
1071 * Set the value for a given offset.
1072 *
1073 * @param mixed $offset Name.
1074 * @param mixed $value Value.
1075 * @return void
1076 */
1077 #[\ReturnTypeWillChange]
1078 public function offsetSet( $offset, $value ) {
1079 $this->setAttribute( $offset, $value );
1080 }
1081
1082 /**
1083 * Unset the value for a given offset.
1084 *
1085 * @param mixed $offset Name.
1086 * @return void
1087 */
1088 #[\ReturnTypeWillChange]
1089 public function offsetUnset( $offset ) {
1090 unset( $this->attributes[ $offset ], $this->relations[ $offset ] );
1091 }
1092
1093 /**
1094 * Determine if an attribute or relation exists on the model.
1095 *
1096 * @param string $key Name.
1097 * @return bool
1098 */
1099 public function __isset( $key ) {
1100 return $this->offsetExists( $key );
1101 }
1102
1103 /**
1104 * Unset an attribute on the model.
1105 *
1106 * @param string $key Name.
1107 * @return void
1108 */
1109 public function __unset( $key ) {
1110 $this->offsetUnset( $key );
1111 }
1112
1113 /**
1114 * Forward call to method
1115 *
1116 * @param string $method Method to call.
1117 * @param mixed $params Method params.
1118 */
1119 public function __call( $method, $params ) {
1120 // if we have the method.
1121 if ( method_exists( $this, $method ) ) {
1122 return call_user_func_array( [ $this, $method ], $params );
1123 }
1124 // if the query builder has a method, start building query.
1125 if ( method_exists( Query::class, $method ) ) {
1126 $result = $this->getQuery()->{$method}( ...$params );
1127 if ( in_array( $method, [ 'count' ] ) ) {
1128 return $result;
1129 }
1130 return $this;
1131 }
1132 }
1133
1134 /**
1135 * Static Facade Accessor
1136 *
1137 * @param string $method Method to call.
1138 * @param mixed $params Method params.
1139 *
1140 * @return mixed
1141 */
1142 public static function __callStatic( $method, $params ) {
1143 // if we have the method.
1144 if ( method_exists( static::class, $method ) ) {
1145 return call_user_func_array( [ new static(), $method ], $params );
1146 }
1147
1148 // if the query builder has a method, start building query.
1149 if ( method_exists( Query::class, $method ) ) {
1150 $item = new static();
1151 $result = $item->getQuery()->{$method}( ...$params );
1152 if ( in_array( $method, [ 'count' ] ) ) {
1153 return $result;
1154 }
1155 return $item;
1156 }
1157 }
1158 }
1159