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