PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.21
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.21
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Database / Orm / Model.php

Model.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.21, at vendor/wpfluent/framework/src/WPFluent/Database/Orm/Model.php

2,168 lines 55.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\Framework\Database\Orm;
4
5 use ArrayAccess;
6 use LogicException;
7 use JsonSerializable;
8 use FluentBooking\Framework\Support\Arr;
9 use FluentBooking\Framework\Support\Str;
10 use FluentBooking\Framework\Support\Helper;
11 use FluentBooking\Framework\Support\ForwardsCalls;
12 use FluentBooking\Framework\Support\UrlRoutable;
13 use FluentBooking\Framework\Support\JsonableInterface;
14 use FluentBooking\Framework\Support\ArrayableInterface;
15 use FluentBooking\Framework\Support\HelperFunctionsTrait;
16 use FluentBooking\Framework\Support\CanBeEscapedWhenCastToString;
17 use FluentBooking\Framework\Support\Collection as BaseCollection;
18 use FluentBooking\Framework\Database\Orm\Relations\Pivot;
19 use FluentBooking\Framework\Database\Orm\Relations\BelongsToMany;
20 use FluentBooking\Framework\Database\Orm\Relations\HasManyThrough;
21 use FluentBooking\Framework\Database\Orm\Relations\Concerns\AsPivot;
22 use FluentBooking\Framework\Database\Orm\Collection as OrmCollection;
23 use FluentBooking\Framework\Database\ConnectionResolverInterface as Resolver;
24
25 abstract class Model implements ArrayableInterface, ArrayAccess, CanBeEscapedWhenCastToString, JsonableInterface, JsonSerializable, UrlRoutable
26 {
27 use HelperFunctionsTrait, ResourceAbleTrait;
28
29 use Concerns\HasAttributes,
30 Concerns\HasEvents,
31 Concerns\HasGlobalScopes,
32 Concerns\HasRelationships,
33 Concerns\HasTimestamps,
34 Concerns\HidesAttributes,
35 Concerns\GuardsAttributes,
36 ForwardsCalls;
37
38 /**
39 * The connection name for the model.
40 *
41 * @var string|null
42 */
43 protected $connection;
44
45 /**
46 * The table associated with the model.
47 *
48 * @var string
49 */
50 protected $table;
51
52 /**
53 * The primary key for the model.
54 *
55 * @var string
56 */
57 protected $primaryKey = 'id';
58
59 /**
60 * The "type" of the primary key ID.
61 *
62 * @var string
63 */
64 protected $keyType = 'int';
65
66 /**
67 * Indicates if the IDs are auto-incrementing.
68 *
69 * @var bool
70 */
71 public $incrementing = true;
72
73 /**
74 * The relations to eager load on every query.
75 *
76 * @var array
77 */
78 protected $with = [];
79
80 /**
81 * The relationship counts that should be eager loaded on every query.
82 *
83 * @var array
84 */
85 protected $withCount = [];
86
87 /**
88 * Indicates whether lazy loading will be prevented on this model.
89 *
90 * @var bool
91 */
92 public $preventsLazyLoading = false;
93
94 /**
95 * The number of models to return for pagination.
96 *
97 * @var int
98 */
99 protected $perPage = 15;
100
101 /**
102 * Indicates if the model exists.
103 *
104 * @var bool
105 */
106 public $exists = false;
107
108 /**
109 * Indicates if the model was inserted during the current request lifecycle.
110 *
111 * @var bool
112 */
113 public $wasRecentlyCreated = false;
114
115 /**
116 * Indicates that the object's string representation should be escaped when __toString is invoked.
117 *
118 * @var bool
119 */
120 protected $escapeWhenCastingToString = false;
121
122 /**
123 * The connection resolver instance.
124 *
125 * @var \FluentBooking\Framework\Database\ConnectionResolverInterface
126 */
127 protected static $resolver;
128
129 /**
130 * The event dispatcher instance.
131 *
132 * @var \FluentBooking\Framework\Events\Dispatcher
133 */
134 protected static $dispatcher;
135
136 /**
137 * The array of booted models.
138 *
139 * @var array
140 */
141 protected static $booted = [];
142
143 /**
144 * The array of trait initializers that will be called on each new instance.
145 *
146 * @var array
147 */
148 protected static $traitInitializers = [];
149
150 /**
151 * The array of global scopes on the model.
152 *
153 * @var array
154 */
155 protected static $globalScopes = [];
156
157 /**
158 * The list of models classes that should not be affected with touch.
159 *
160 * @var array
161 */
162 protected static $ignoreOnTouch = [];
163
164 /**
165 * Indicates whether lazy loading should be restricted on all models.
166 *
167 * @var bool
168 */
169 protected static $modelsShouldPreventLazyLoading = false;
170
171 /**
172 * The callback that is responsible for handling lazy loading violations.
173 *
174 * @var callable|null
175 */
176 protected static $lazyLoadingViolationCallback;
177
178 /**
179 * Indicates if broadcasting is currently enabled.
180 *
181 * @var bool
182 */
183 protected static $isBroadcasting = true;
184
185 /**
186 * The name of the "created at" column.
187 *
188 * @var string|null
189 */
190 const CREATED_AT = 'created_at';
191
192 /**
193 * The name of the "updated at" column.
194 *
195 * @var string|null
196 */
197 const UPDATED_AT = 'updated_at';
198
199 /**
200 * Create a new Orm model instance.
201 *
202 * @param array $attributes
203 * @return void
204 */
205 public function __construct(array $attributes = [])
206 {
207 $this->bootIfNotBooted();
208
209 $this->initializeTraits();
210
211 $this->syncOriginal();
212
213 $this->fill($attributes);
214 }
215
216 /**
217 * Check if the model needs to be booted and if so, do it.
218 *
219 * @return void
220 */
221 protected function bootIfNotBooted()
222 {
223 if (! isset(static::$booted[static::class])) {
224 static::$booted[static::class] = true;
225
226 $this->fireModelEvent('booting', false);
227
228 static::booting();
229 static::boot();
230 static::booted();
231
232 $this->fireModelEvent('booted', false);
233 }
234 }
235
236 /**
237 * Perform any actions required before the model boots.
238 *
239 * @return void
240 */
241 protected static function booting()
242 {
243 //
244 }
245
246 /**
247 * Bootstrap the model and its traits.
248 *
249 * @return void
250 */
251 protected static function boot()
252 {
253 static::bootTraits();
254 }
255
256 /**
257 * Boot all of the bootable traits on the model.
258 *
259 * @return void
260 */
261 protected static function bootTraits()
262 {
263 $class = static::class;
264
265 $booted = [];
266
267 static::$traitInitializers[$class] = [];
268
269 foreach (static::classUsesRecursive($class) as $trait) {
270 $method = 'boot'.static::classBasename($trait);
271
272 if (method_exists($class, $method) && ! in_array($method, $booted)) {
273 forward_static_call([$class, $method]);
274
275 $booted[] = $method;
276 }
277
278 if (method_exists($class, $method = 'initialize'.static::classBasename($trait))) {
279 static::$traitInitializers[$class][] = $method;
280
281 static::$traitInitializers[$class] = array_unique(
282 static::$traitInitializers[$class]
283 );
284 }
285 }
286 }
287
288 /**
289 * Initialize any initializable traits on the model.
290 *
291 * @return void
292 */
293 protected function initializeTraits()
294 {
295 foreach (static::$traitInitializers[static::class] as $method) {
296 $this->{$method}();
297 }
298 }
299
300 /**
301 * Perform any actions required after the model boots.
302 *
303 * @return void
304 */
305 protected static function booted()
306 {
307 //
308 }
309
310 /**
311 * Clear the list of booted models so they will be re-booted.
312 *
313 * @return void
314 */
315 public static function clearBootedModels()
316 {
317 static::$booted = [];
318
319 static::$globalScopes = [];
320 }
321
322 /**
323 * Disables relationship model touching for the current class during given callback scope.
324 *
325 * @param callable $callback
326 * @return void
327 */
328 public static function withoutTouching(callable $callback)
329 {
330 static::withoutTouchingOn([static::class], $callback);
331 }
332
333 /**
334 * Disables relationship model touching for the given model classes during given callback scope.
335 *
336 * @param array $models
337 * @param callable $callback
338 * @return void
339 */
340 public static function withoutTouchingOn(array $models, callable $callback)
341 {
342 static::$ignoreOnTouch = array_values(array_merge(static::$ignoreOnTouch, $models));
343
344 try {
345 $callback();
346 } finally {
347 static::$ignoreOnTouch = array_values(array_diff(static::$ignoreOnTouch, $models));
348 }
349 }
350
351 /**
352 * Determine if the given model is ignoring touches.
353 *
354 * @param string|null $class
355 * @return bool
356 */
357 public static function isIgnoringTouch($class = null)
358 {
359 $class = $class ?: static::class;
360
361 if (! get_class_vars($class)['timestamps'] || ! $class::UPDATED_AT) {
362 return true;
363 }
364
365 foreach (static::$ignoreOnTouch as $ignoredClass) {
366 if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) {
367 return true;
368 }
369 }
370
371 return false;
372 }
373
374 /**
375 * Prevent model relationships from being lazy loaded.
376 *
377 * @param bool $value
378 * @return void
379 */
380 public static function preventLazyLoading($value = true)
381 {
382 static::$modelsShouldPreventLazyLoading = $value;
383 }
384
385 /**
386 * Register a callback that is responsible for handling lazy loading violations.
387 *
388 * @param callable|null $callback
389 * @return void
390 */
391 public static function handleLazyLoadingViolationUsing(?callable $callback)
392 {
393 static::$lazyLoadingViolationCallback = $callback;
394 }
395
396 /**
397 * Execute a callback without broadcasting any model events for all model types.
398 *
399 * @param callable $callback
400 * @return mixed
401 */
402 public static function withoutBroadcasting(callable $callback)
403 {
404 $isBroadcasting = static::$isBroadcasting;
405
406 static::$isBroadcasting = false;
407
408 try {
409 return $callback();
410 } finally {
411 static::$isBroadcasting = $isBroadcasting;
412 }
413 }
414
415 /**
416 * Fill the model with an array of attributes.
417 *
418 * @param array $attributes
419 * @return $this
420 *
421 * @throws \FluentBooking\Framework\Database\Orm\MassAssignmentException
422 */
423 public function fill(array $attributes)
424 {
425 $totallyGuarded = $this->totallyGuarded();
426
427 foreach ($this->fillableFromArray($attributes) as $key => $value) {
428 // The developers may choose to place some attributes in the "fillable" array
429 // which means only those attributes may be set through mass assignment to
430 // the model, and all others will just get ignored for security reasons.
431 if ($this->isFillable($key)) {
432 $this->setAttribute($key, $value);
433 } elseif ($totallyGuarded) {
434 throw new MassAssignmentException(sprintf(
435 'Add [%s] to fillable property to allow mass assignment on [%s].',
436 $key, get_class($this)
437 ));
438 }
439 }
440
441 return $this;
442 }
443
444 /**
445 * Fill the model with an array of attributes. Force mass assignment.
446 *
447 * @param array $attributes
448 * @return $this
449 */
450 public function forceFill(array $attributes)
451 {
452 return static::unguarded(function () use ($attributes) {
453 return $this->fill($attributes);
454 });
455 }
456
457 /**
458 * Qualify the given column name by the model's table.
459 *
460 * @param string $column
461 * @return string
462 */
463 public function qualifyColumn($column)
464 {
465 if (Str::contains($column, '.')) {
466 return $column;
467 }
468
469 return $this->getTable().'.'.$column;
470 }
471
472 /**
473 * Qualify the given columns with the model's table.
474 *
475 * @param array $columns
476 * @return array
477 */
478 public function qualifyColumns($columns)
479 {
480 return Helper::collect($columns)->map(function ($column) {
481 return $this->qualifyColumn($column);
482 })->all();
483 }
484
485 /**
486 * Create a new instance of the given model.
487 *
488 * @param array $attributes
489 * @param bool $exists
490 * @return static
491 */
492 public function newInstance($attributes = [], $exists = false)
493 {
494 // This method just provides a convenient way for us to generate fresh model
495 // instances of this current model. It is particularly useful during the
496 // hydration of new objects via the Orm query builder instances.
497 $model = new static((array) $attributes);
498
499 $model->exists = $exists;
500
501 $model->setConnection(
502 $this->getConnectionName()
503 );
504
505 $model->setTable($this->getTable());
506
507 $model->mergeCasts($this->casts);
508
509 return $model;
510 }
511
512 /**
513 * Create a new model instance that is existing.
514 *
515 * @param array $attributes
516 * @param string|null $connection
517 * @return static
518 */
519 public function newFromBuilder($attributes = [], $connection = null)
520 {
521 $model = $this->newInstance([], true);
522
523 $model->setRawAttributes((array) $attributes, true);
524
525 $model->setConnection($connection ?: $this->getConnectionName());
526
527 $model->fireModelEvent('retrieved', false);
528
529 return $model;
530 }
531
532 /**
533 * Begin querying the model on a given connection.
534 *
535 * @param string|null $connection
536 * @return \FluentBooking\Framework\Database\Orm\Builder
537 */
538 public static function on($connection = null)
539 {
540 // First we will just create a fresh instance of this model, and then we can set the
541 // connection on the model so that it is used for the queries we execute, as well
542 // as being set on every relation we retrieve without a custom connection name.
543 $instance = new static;
544
545 $instance->setConnection($connection);
546
547 return $instance->newQuery();
548 }
549
550 /**
551 * Begin querying the model on the write connection.
552 *
553 * @return \FluentBooking\Framework\Database\Query\Builder
554 */
555 public static function onWriteConnection()
556 {
557 return static::query()->useWritePdo();
558 }
559
560 /**
561 * Get all of the models from the database.
562 *
563 * @param array|mixed $columns
564 * @return \FluentBooking\Framework\Database\Orm\Collection|static[]
565 */
566 public static function all($columns = ['*'])
567 {
568 return static::query()->get(
569 is_array($columns) ? $columns : func_get_args()
570 );
571 }
572
573 /**
574 * Begin querying a model with eager loading.
575 *
576 * @param array|string $relations
577 * @return \FluentBooking\Framework\Database\Orm\Builder
578 */
579 public static function with($relations)
580 {
581 return static::query()->with(
582 is_string($relations) ? func_get_args() : $relations
583 );
584 }
585
586 /**
587 * Eager load relations on the model.
588 *
589 * @param array|string $relations
590 * @return $this
591 */
592 public function load($relations)
593 {
594 $query = $this->newQueryWithoutRelationships()->with(
595 is_string($relations) ? func_get_args() : $relations
596 );
597
598 $query->eagerLoadRelations([$this]);
599
600 return $this;
601 }
602
603 /**
604 * Eager load relationships on the polymorphic relation of a model.
605 *
606 * @param string $relation
607 * @param array $relations
608 * @return $this
609 */
610 public function loadMorph($relation, $relations)
611 {
612 if (! $this->{$relation}) {
613 return $this;
614 }
615
616 $className = get_class($this->{$relation});
617
618 $this->{$relation}->load($relations[$className] ?? []);
619
620 return $this;
621 }
622
623 /**
624 * Eager load relations on the model if they are not already eager loaded.
625 *
626 * @param array|string $relations
627 * @return $this
628 */
629 public function loadMissing($relations)
630 {
631 $relations = is_string($relations) ? func_get_args() : $relations;
632
633 $this->newCollection([$this])->loadMissing($relations);
634
635 return $this;
636 }
637
638 /**
639 * Eager load relation's column aggregations on the model.
640 *
641 * @param array|string $relations
642 * @param string $column
643 * @param string $function
644 * @return $this
645 */
646 public function loadAggregate($relations, $column, $function = null)
647 {
648 $this->newCollection([$this])->loadAggregate($relations, $column, $function);
649
650 return $this;
651 }
652
653 /**
654 * Eager load relation counts on the model.
655 *
656 * @param array|string $relations
657 * @return $this
658 */
659 public function loadCount($relations)
660 {
661 $relations = is_string($relations) ? func_get_args() : $relations;
662
663 return $this->loadAggregate($relations, '*', 'count');
664 }
665
666 /**
667 * Eager load relation max column values on the model.
668 *
669 * @param array|string $relations
670 * @param string $column
671 * @return $this
672 */
673 public function loadMax($relations, $column)
674 {
675 return $this->loadAggregate($relations, $column, 'max');
676 }
677
678 /**
679 * Eager load relation min column values on the model.
680 *
681 * @param array|string $relations
682 * @param string $column
683 * @return $this
684 */
685 public function loadMin($relations, $column)
686 {
687 return $this->loadAggregate($relations, $column, 'min');
688 }
689
690 /**
691 * Eager load relation's column summations on the model.
692 *
693 * @param array|string $relations
694 * @param string $column
695 * @return $this
696 */
697 public function loadSum($relations, $column)
698 {
699 return $this->loadAggregate($relations, $column, 'sum');
700 }
701
702 /**
703 * Eager load relation average column values on the model.
704 *
705 * @param array|string $relations
706 * @param string $column
707 * @return $this
708 */
709 public function loadAvg($relations, $column)
710 {
711 return $this->loadAggregate($relations, $column, 'avg');
712 }
713
714 /**
715 * Eager load related model existence values on the model.
716 *
717 * @param array|string $relations
718 * @return $this
719 */
720 public function loadExists($relations)
721 {
722 return $this->loadAggregate($relations, '*', 'exists');
723 }
724
725 /**
726 * Eager load relationship column aggregation on the polymorphic relation of a model.
727 *
728 * @param string $relation
729 * @param array $relations
730 * @param string $column
731 * @param string $function
732 * @return $this
733 */
734 public function loadMorphAggregate($relation, $relations, $column, $function = null)
735 {
736 if (! $this->{$relation}) {
737 return $this;
738 }
739
740 $className = get_class($this->{$relation});
741
742 $this->{$relation}->loadAggregate($relations[$className] ?? [], $column, $function);
743
744 return $this;
745 }
746
747 /**
748 * Eager load relationship counts on the polymorphic relation of a model.
749 *
750 * @param string $relation
751 * @param array $relations
752 * @return $this
753 */
754 public function loadMorphCount($relation, $relations)
755 {
756 return $this->loadMorphAggregate($relation, $relations, '*', 'count');
757 }
758
759 /**
760 * Eager load relationship max column values on the polymorphic relation of a model.
761 *
762 * @param string $relation
763 * @param array $relations
764 * @param string $column
765 * @return $this
766 */
767 public function loadMorphMax($relation, $relations, $column)
768 {
769 return $this->loadMorphAggregate($relation, $relations, $column, 'max');
770 }
771
772 /**
773 * Eager load relationship min column values on the polymorphic relation of a model.
774 *
775 * @param string $relation
776 * @param array $relations
777 * @param string $column
778 * @return $this
779 */
780 public function loadMorphMin($relation, $relations, $column)
781 {
782 return $this->loadMorphAggregate($relation, $relations, $column, 'min');
783 }
784
785 /**
786 * Eager load relationship column summations on the polymorphic relation of a model.
787 *
788 * @param string $relation
789 * @param array $relations
790 * @param string $column
791 * @return $this
792 */
793 public function loadMorphSum($relation, $relations, $column)
794 {
795 return $this->loadMorphAggregate($relation, $relations, $column, 'sum');
796 }
797
798 /**
799 * Eager load relationship average column values on the polymorphic relation of a model.
800 *
801 * @param string $relation
802 * @param array $relations
803 * @param string $column
804 * @return $this
805 */
806 public function loadMorphAvg($relation, $relations, $column)
807 {
808 return $this->loadMorphAggregate($relation, $relations, $column, 'avg');
809 }
810
811 /**
812 * Increment a column's value by a given amount.
813 *
814 * @param string $column
815 * @param float|int $amount
816 * @param array $extra
817 * @return int
818 */
819 protected function increment($column, $amount = 1, array $extra = [])
820 {
821 return $this->incrementOrDecrement($column, $amount, $extra, 'increment');
822 }
823
824 /**
825 * Decrement a column's value by a given amount.
826 *
827 * @param string $column
828 * @param float|int $amount
829 * @param array $extra
830 * @return int
831 */
832 protected function decrement($column, $amount = 1, array $extra = [])
833 {
834 return $this->incrementOrDecrement($column, $amount, $extra, 'decrement');
835 }
836
837 /**
838 * Run the increment or decrement method on the model.
839 *
840 * @param string $column
841 * @param float|int $amount
842 * @param array $extra
843 * @param string $method
844 * @return int
845 */
846 protected function incrementOrDecrement($column, $amount, $extra, $method)
847 {
848 $query = $this->newQueryWithoutRelationships();
849
850 if (! $this->exists) {
851 return $query->{$method}($column, $amount, $extra);
852 }
853
854 $this->{$column} = $this->isClassDeviable($column)
855 ? $this->deviateClassCastableAttribute($method, $column, $amount)
856 : $this->{$column} + ($method === 'increment' ? $amount : $amount * -1);
857
858 $this->forceFill($extra);
859
860 if ($this->fireModelEvent('updating') === false) {
861 return false;
862 }
863
864 return Helper::tap($this->setKeysForSaveQuery($query)->{$method}($column, $amount, $extra), function () use ($column) {
865 $this->syncChanges();
866
867 $this->fireModelEvent('updated', false);
868
869 $this->syncOriginalAttribute($column);
870 });
871 }
872
873 /**
874 * Update the model in the database.
875 *
876 * @param array $attributes
877 * @param array $options
878 * @return bool
879 */
880 public function update(array $attributes = [], array $options = [])
881 {
882 if (! $this->exists) {
883 return false;
884 }
885
886 return $this->fill($attributes)->save($options);
887 }
888
889 /**
890 * Update the model in the database within a transaction.
891 *
892 * @param array $attributes
893 * @param array $options
894 * @return bool
895 *
896 * @throws \Throwable
897 */
898 public function updateOrFail(array $attributes = [], array $options = [])
899 {
900 if (! $this->exists) {
901 return false;
902 }
903
904 return $this->fill($attributes)->saveOrFail($options);
905 }
906
907 /**
908 * Update the model in the database without raising any events.
909 *
910 * @param array $attributes
911 * @param array $options
912 * @return bool
913 */
914 public function updateQuietly(array $attributes = [], array $options = [])
915 {
916 if (! $this->exists) {
917 return false;
918 }
919
920 return $this->fill($attributes)->saveQuietly($options);
921 }
922
923 /**
924 * Save the model and all of its relationships.
925 *
926 * @return bool
927 */
928 public function push()
929 {
930 if (! $this->save()) {
931 return false;
932 }
933
934 // To sync all of the relationships to the database, we will simply spin through
935 // the relationships and save each model via this "push" method, which allows
936 // us to recurse into all of these nested relations for the model instance.
937 foreach ($this->relations as $models) {
938 $models = $models instanceof Collection
939 ? $models->all() : [$models];
940
941 foreach (array_filter($models) as $model) {
942 if (! $model->push()) {
943 return false;
944 }
945 }
946 }
947
948 return true;
949 }
950
951 /**
952 * Save the model to the database without raising any events.
953 *
954 * @param array $options
955 * @return bool
956 */
957 public function saveQuietly(array $options = [])
958 {
959 return static::withoutEvents(function () use ($options) {
960 return $this->save($options);
961 });
962 }
963
964 /**
965 * Save the model to the database.
966 *
967 * @param array $options
968 * @return bool
969 */
970 public function save(array $options = [])
971 {
972 $this->mergeAttributesFromCachedCasts();
973
974 $query = $this->newModelQuery();
975
976 // If the "saving" event returns false we'll bail out of the save and return
977 // false, indicating that the save failed. This provides a chance for any
978 // listeners to cancel save operations if validations fail or whatever.
979 if ($this->fireModelEvent('saving') === false) {
980 return false;
981 }
982
983 // If the model already exists in the database we can just update our record
984 // that is already in this database using the current IDs in this "where"
985 // clause to only update this model. Otherwise, we'll just insert them.
986 if ($this->exists) {
987 $saved = $this->isDirty() ?
988 $this->performUpdate($query) : true;
989 }
990
991 // If the model is brand new, we'll insert it into our database and set the
992 // ID attribute on the model to the value of the newly inserted row's ID
993 // which is typically an auto-increment value managed by the database.
994 else {
995 $saved = $this->performInsert($query);
996
997 if (! $this->getConnectionName() &&
998 $connection = $query->getConnection()) {
999 $this->setConnection($connection->getName());
1000 }
1001 }
1002
1003 // If the model is successfully saved, we need to do a few more things once
1004 // that is done. We will call the "saved" method here to run any actions
1005 // we need to happen after a model gets successfully saved right here.
1006 if ($saved) {
1007 $this->finishSave($options);
1008 }
1009
1010 return $saved;
1011 }
1012
1013 /**
1014 * Save the model to the database within a transaction.
1015 *
1016 * @param array $options
1017 * @return bool
1018 *
1019 * @throws \Throwable
1020 */
1021 public function saveOrFail(array $options = [])
1022 {
1023 return $this->getConnection()->transaction(function () use ($options) {
1024 return $this->save($options);
1025 });
1026 }
1027
1028 /**
1029 * Perform any actions that are necessary after the model is saved.
1030 *
1031 * @param array $options
1032 * @return void
1033 */
1034 protected function finishSave(array $options)
1035 {
1036 $this->fireModelEvent('saved', false);
1037
1038 if ($this->isDirty() && ($options['touch'] ?? true)) {
1039 $this->touchOwners();
1040 }
1041
1042 $this->syncOriginal();
1043 }
1044
1045 /**
1046 * Perform a model update operation.
1047 *
1048 * @param \FluentBooking\Framework\Database\Orm\Builder $query
1049 * @return bool
1050 */
1051 protected function performUpdate(Builder $query)
1052 {
1053 // If the updating event returns false, we will cancel the update operation so
1054 // developers can hook Validation systems into their models and cancel this
1055 // operation if the model does not pass validation. Otherwise, we update.
1056 if ($this->fireModelEvent('updating') === false) {
1057 return false;
1058 }
1059
1060 // First we need to create a fresh query instance and touch the creation and
1061 // update timestamp on the model which are maintained by us for developer
1062 // convenience. Then we will just continue saving the model instances.
1063 if ($this->usesTimestamps()) {
1064 $this->updateTimestamps();
1065 }
1066
1067 // Once we have run the update operation, we will fire the "updated" event for
1068 // this model instance. This will allow developers to hook into these after
1069 // models are updated, giving them a chance to do any special processing.
1070 $dirty = $this->getDirty();
1071
1072 if (count($dirty) > 0) {
1073 $this->setKeysForSaveQuery($query)->update($dirty);
1074
1075 $this->syncChanges();
1076
1077 $this->fireModelEvent('updated', false);
1078 }
1079
1080 return true;
1081 }
1082
1083 /**
1084 * Set the keys for a select query.
1085 *
1086 * @param \FluentBooking\Framework\Database\Orm\Builder $query
1087 * @return \FluentBooking\Framework\Database\Orm\Builder
1088 */
1089 protected function setKeysForSelectQuery($query)
1090 {
1091 $query->where($this->getKeyName(), '=', $this->getKeyForSelectQuery());
1092
1093 return $query;
1094 }
1095
1096 /**
1097 * Get the primary key value for a select query.
1098 *
1099 * @return mixed
1100 */
1101 protected function getKeyForSelectQuery()
1102 {
1103 return $this->original[$this->getKeyName()] ?? $this->getKey();
1104 }
1105
1106 /**
1107 * Set the keys for a save update query.
1108 *
1109 * @param \FluentBooking\Framework\Database\Orm\Builder $query
1110 * @return \FluentBooking\Framework\Database\Orm\Builder
1111 */
1112 protected function setKeysForSaveQuery($query)
1113 {
1114 $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery());
1115
1116 return $query;
1117 }
1118
1119 /**
1120 * Get the primary key value for a save query.
1121 *
1122 * @return mixed
1123 */
1124 protected function getKeyForSaveQuery()
1125 {
1126 return $this->original[$this->getKeyName()] ?? $this->getKey();
1127 }
1128
1129 /**
1130 * Perform a model insert operation.
1131 *
1132 * @param \FluentBooking\Framework\Database\Orm\Builder $query
1133 * @return bool
1134 */
1135 protected function performInsert(Builder $query)
1136 {
1137 if ($this->fireModelEvent('creating') === false) {
1138 return false;
1139 }
1140
1141 // First we'll need to create a fresh query instance and touch the creation and
1142 // update timestamps on this model, which are maintained by us for developer
1143 // convenience. After, we will just continue saving these model instances.
1144 if ($this->usesTimestamps()) {
1145 $this->updateTimestamps();
1146 }
1147
1148 // If the model has an incrementing key, we can use the "insertGetId" method on
1149 // the query builder, which will give us back the final inserted ID for this
1150 // table from the database. Not all tables have to be incrementing though.
1151 $attributes = $this->getAttributesForInsert();
1152
1153 if ($this->getIncrementing()) {
1154 $this->insertAndSetId($query, $attributes);
1155 }
1156
1157 // If the table isn't incrementing we'll simply insert these attributes as they
1158 // are. These attribute arrays must contain an "id" column previously placed
1159 // there by the developer as the manually determined key for these models.
1160 else {
1161 if (empty($attributes)) {
1162 return true;
1163 }
1164
1165 $query->insert($attributes);
1166 }
1167
1168 // We will go ahead and set the exists property to true, so that it is set when
1169 // the created event is fired, just in case the developer tries to update it
1170 // during the event. This will allow them to do so and run an update here.
1171 $this->exists = true;
1172
1173 $this->wasRecentlyCreated = true;
1174
1175 $this->fireModelEvent('created', false);
1176
1177 return true;
1178 }
1179
1180 /**
1181 * Insert the given attributes and set the ID on the model.
1182 *
1183 * @param \FluentBooking\Framework\Database\Orm\Builder $query
1184 * @param array $attributes
1185 * @return void
1186 */
1187 protected function insertAndSetId(Builder $query, $attributes)
1188 {
1189 $id = $query->insertGetId($attributes, $keyName = $this->getKeyName());
1190
1191 $this->setAttribute($keyName, $id);
1192 }
1193
1194 /**
1195 * Destroy the models for the given IDs.
1196 *
1197 * @param \FluentBooking\Framework\Support\Collection|array|int|string $ids
1198 * @return int
1199 */
1200 public static function destroy($ids)
1201 {
1202 if ($ids instanceof OrmCollection) {
1203 $ids = $ids->modelKeys();
1204 }
1205
1206 if ($ids instanceof BaseCollection) {
1207 $ids = $ids->all();
1208 }
1209
1210 $ids = is_array($ids) ? $ids : func_get_args();
1211
1212 if (count($ids) === 0) {
1213 return 0;
1214 }
1215
1216 // We will actually pull the models from the database table and call delete on
1217 // each of them individually so that their events get fired properly with a
1218 // correct set of attributes in case the developers wants to check these.
1219 $key = ($instance = new static)->getKeyName();
1220
1221 $count = 0;
1222
1223 foreach ($instance->whereIn($key, $ids)->get() as $model) {
1224 if ($model->delete()) {
1225 $count++;
1226 }
1227 }
1228
1229 return $count;
1230 }
1231
1232 /**
1233 * Delete the model from the database.
1234 *
1235 * @return bool|null
1236 *
1237 * @throws \LogicException
1238 */
1239 public function delete()
1240 {
1241 $this->mergeAttributesFromCachedCasts();
1242
1243 if (is_null($this->getKeyName())) {
1244 throw new LogicException('No primary key defined on model.');
1245 }
1246
1247 // If the model doesn't exist, there is nothing to delete so we'll just return
1248 // immediately and not do anything else. Otherwise, we will continue with a
1249 // deletion process on the model, firing the proper events, and so forth.
1250 if (! $this->exists) {
1251 return;
1252 }
1253
1254 if ($this->fireModelEvent('deleting') === false) {
1255 return false;
1256 }
1257
1258 // Here, we'll touch the owning models, verifying these timestamps get updated
1259 // for the models. This will allow any caching to get broken on the parents
1260 // by the timestamp. Then we will go ahead and delete the model instance.
1261 $this->touchOwners();
1262
1263 $this->performDeleteOnModel();
1264
1265 // Once the model has been deleted, we will fire off the deleted event so that
1266 // the developers may hook into post-delete operations. We will then return
1267 // a boolean true as the delete is presumably successful on the database.
1268 $this->fireModelEvent('deleted', false);
1269
1270 return true;
1271 }
1272
1273 /**
1274 * Delete the model from the database within a transaction.
1275 *
1276 * @return bool|null
1277 *
1278 * @throws \Throwable
1279 */
1280 public function deleteOrFail()
1281 {
1282 if (! $this->exists) {
1283 return false;
1284 }
1285
1286 return $this->getConnection()->transaction(function () {
1287 return $this->delete();
1288 });
1289 }
1290
1291 /**
1292 * Force a hard delete on a soft deleted model.
1293 *
1294 * This method protects developers from running forceDelete when the trait is missing.
1295 *
1296 * @return bool|null
1297 */
1298 public function forceDelete()
1299 {
1300 return $this->delete();
1301 }
1302
1303 /**
1304 * Perform the actual delete query on this model instance.
1305 *
1306 * @return void
1307 */
1308 protected function performDeleteOnModel()
1309 {
1310 $this->setKeysForSaveQuery($this->newModelQuery())->delete();
1311
1312 $this->exists = false;
1313 }
1314
1315 /**
1316 * Begin querying the model.
1317 *
1318 * @return \FluentBooking\Framework\Database\Orm\Builder
1319 */
1320 public static function query()
1321 {
1322 return (new static)->newQuery();
1323 }
1324
1325 /**
1326 * Get a new query builder for the model's table.
1327 *
1328 * @return \FluentBooking\Framework\Database\Orm\Builder
1329 */
1330 public function newQuery()
1331 {
1332 return $this->registerGlobalScopes($this->newQueryWithoutScopes());
1333 }
1334
1335 /**
1336 * Get a new query builder that doesn't have any global scopes or eager loading.
1337 *
1338 * @return \FluentBooking\Framework\Database\Orm\Builder|static
1339 */
1340 public function newModelQuery()
1341 {
1342 return $this->newOrmBuilder(
1343 $this->newBaseQueryBuilder()
1344 )->setModel($this);
1345 }
1346
1347 /**
1348 * Get a new query builder with no relationships loaded.
1349 *
1350 * @return \FluentBooking\Framework\Database\Orm\Builder
1351 */
1352 public function newQueryWithoutRelationships()
1353 {
1354 return $this->registerGlobalScopes($this->newModelQuery());
1355 }
1356
1357 /**
1358 * Register the global scopes for this builder instance.
1359 *
1360 * @param \FluentBooking\Framework\Database\Orm\Builder $builder
1361 * @return \FluentBooking\Framework\Database\Orm\Builder
1362 */
1363 public function registerGlobalScopes($builder)
1364 {
1365 foreach ($this->getGlobalScopes() as $identifier => $scope) {
1366 $builder->withGlobalScope($identifier, $scope);
1367 }
1368
1369 return $builder;
1370 }
1371
1372 /**
1373 * Get a new query builder that doesn't have any global scopes.
1374 *
1375 * @return \FluentBooking\Framework\Database\Orm\Builder|static
1376 */
1377 public function newQueryWithoutScopes()
1378 {
1379 return $this->newModelQuery()
1380 ->with($this->with)
1381 ->withCount($this->withCount);
1382 }
1383
1384 /**
1385 * Get a new query instance without a given scope.
1386 *
1387 * @param \FluentBooking\Framework\Database\Orm\Scope|string $scope
1388 * @return \FluentBooking\Framework\Database\Orm\Builder
1389 */
1390 public function newQueryWithoutScope($scope)
1391 {
1392 return $this->newQuery()->withoutGlobalScope($scope);
1393 }
1394
1395 /**
1396 * Get a new query to restore one or more models by their queueable IDs.
1397 *
1398 * @param array|int $ids
1399 * @return \FluentBooking\Framework\Database\Orm\Builder
1400 */
1401 public function newQueryForRestoration($ids)
1402 {
1403 return is_array($ids)
1404 ? $this->newQueryWithoutScopes()->whereIn($this->getQualifiedKeyName(), $ids)
1405 : $this->newQueryWithoutScopes()->whereKey($ids);
1406 }
1407
1408 /**
1409 * Create a new Orm query builder for the model.
1410 *
1411 * @param \FluentBooking\Framework\Database\Query\Builder $query
1412 * @return \FluentBooking\Framework\Database\Orm\Builder|static
1413 */
1414 public function newOrmBuilder($query)
1415 {
1416 return new Builder($query);
1417 }
1418
1419 /**
1420 * Get a new query builder instance for the connection.
1421 *
1422 * @return \FluentBooking\Framework\Database\Query\Builder
1423 */
1424 protected function newBaseQueryBuilder()
1425 {
1426 return $this->getConnection()->query();
1427 }
1428
1429 /**
1430 * Create a new Orm Collection instance.
1431 *
1432 * @param array $models
1433 * @return \FluentBooking\Framework\Database\Orm\Collection
1434 */
1435 public function newCollection(array $models = [])
1436 {
1437 return new Collection($models);
1438 }
1439
1440 /**
1441 * Create a new pivot model instance.
1442 *
1443 * @param \FluentBooking\Framework\Database\Orm\Model $parent
1444 * @param array $attributes
1445 * @param string $table
1446 * @param bool $exists
1447 * @param string|null $using
1448 * @return \FluentBooking\Framework\Database\Orm\Relations\Pivot
1449 */
1450 public function newPivot(self $parent, array $attributes, $table, $exists, $using = null)
1451 {
1452 return $using ? $using::fromRawAttributes($parent, $attributes, $table, $exists)
1453 : Pivot::fromAttributes($parent, $attributes, $table, $exists);
1454 }
1455
1456 /**
1457 * Determine if the model has a given scope.
1458 *
1459 * @param string $scope
1460 * @return bool
1461 */
1462 public function hasNamedScope($scope)
1463 {
1464 return method_exists($this, 'scope'.ucfirst($scope));
1465 }
1466
1467 /**
1468 * Apply the given named scope if possible.
1469 *
1470 * @param string $scope
1471 * @param array $parameters
1472 * @return mixed
1473 */
1474 public function callNamedScope($scope, array $parameters = [])
1475 {
1476 return $this->{'scope'.ucfirst($scope)}(...$parameters);
1477 }
1478
1479 /**
1480 * Convert the model instance to an array.
1481 *
1482 * @return array
1483 */
1484 public function toArray()
1485 {
1486 return array_merge($this->attributesToArray(), $this->relationsToArray());
1487 }
1488
1489 /**
1490 * Convert the model instance to JSON.
1491 *
1492 * @param int $options
1493 * @return string
1494 *
1495 * @throws \FluentBooking\Framework\Database\Orm\JsonEncodingException
1496 */
1497 public function toJson($options = 0)
1498 {
1499 $json = json_encode($this->jsonSerialize(), $options);
1500
1501 if (JSON_ERROR_NONE !== json_last_error()) {
1502 throw JsonEncodingException::forModel($this, json_last_error_msg());
1503 }
1504
1505 return $json;
1506 }
1507
1508 /**
1509 * Convert the object into something JSON serializable.
1510 *
1511 * @return array
1512 */
1513 #[\ReturnTypeWillChange]
1514 public function jsonSerialize()
1515 {
1516 return $this->toArray();
1517 }
1518
1519 /**
1520 * Reload a fresh model instance from the database.
1521 *
1522 * @param array|string $with
1523 * @return static|null
1524 */
1525 public function fresh($with = [])
1526 {
1527 if (! $this->exists) {
1528 return;
1529 }
1530
1531 return $this->setKeysForSelectQuery($this->newQueryWithoutScopes())
1532 ->with(is_string($with) ? func_get_args() : $with)
1533 ->first();
1534 }
1535
1536 /**
1537 * Reload the current model instance with fresh attributes from the database.
1538 *
1539 * @return $this
1540 */
1541 public function refresh()
1542 {
1543 if (! $this->exists) {
1544 return $this;
1545 }
1546
1547 $this->setRawAttributes(
1548 $this->setKeysForSelectQuery($this->newQueryWithoutScopes())->firstOrFail()->attributes
1549 );
1550
1551 $this->load(Helper::collect($this->relations)->reject(function ($relation) {
1552 return $relation instanceof Pivot
1553 || (is_object($relation) && in_array(AsPivot::class, static::classUsesRecursive($relation), true));
1554 })->keys()->all());
1555
1556 $this->syncOriginal();
1557
1558 return $this;
1559 }
1560
1561 /**
1562 * Clone the model into a new, non-existing instance.
1563 *
1564 * @param array|null $except
1565 * @return static
1566 */
1567 public function replicate(array $except = null)
1568 {
1569 $defaults = [
1570 $this->getKeyName(),
1571 $this->getCreatedAtColumn(),
1572 $this->getUpdatedAtColumn(),
1573 ];
1574
1575 $attributes = Arr::except(
1576 $this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults
1577 );
1578
1579 return Helper::tap(new static, function ($instance) use ($attributes) {
1580 $instance->setRawAttributes($attributes);
1581
1582 $instance->setRelations($this->relations);
1583
1584 $instance->fireModelEvent('replicating', false);
1585 });
1586 }
1587
1588 /**
1589 * Determine if two models have the same ID and belong to the same table.
1590 *
1591 * @param \FluentBooking\Framework\Database\Orm\Model|null $model
1592 * @return bool
1593 */
1594 public function is($model)
1595 {
1596 return ! is_null($model) &&
1597 $this->getKey() === $model->getKey() &&
1598 $this->getTable() === $model->getTable() &&
1599 $this->getConnectionName() === $model->getConnectionName();
1600 }
1601
1602 /**
1603 * Determine if two models are not the same.
1604 *
1605 * @param \FluentBooking\Framework\Database\Orm\Model|null $model
1606 * @return bool
1607 */
1608 public function isNot($model)
1609 {
1610 return ! $this->is($model);
1611 }
1612
1613 /**
1614 * Get the database connection for the model.
1615 *
1616 * @return \FluentBooking\Framework\Database\Query\WPDBConnection
1617 */
1618 public function getConnection()
1619 {
1620 return static::resolveConnection($this->getConnectionName());
1621 }
1622
1623 /**
1624 * Get the current connection name for the model.
1625 *
1626 * @return string|null
1627 */
1628 public function getConnectionName()
1629 {
1630 return $this->connection;
1631 }
1632
1633 /**
1634 * Set the connection associated with the model.
1635 *
1636 * @param string|null $name
1637 * @return $this
1638 */
1639 public function setConnection($name)
1640 {
1641 $this->connection = $name;
1642
1643 return $this;
1644 }
1645
1646 /**
1647 * Resolve a connection instance.
1648 *
1649 * @param string|null $connection
1650 * @return \FluentBooking\Framework\Database\Query\WPDBConnection
1651 */
1652 public static function resolveConnection($connection = null)
1653 {
1654 return static::$resolver->connection($connection);
1655 }
1656
1657 /**
1658 * Get the connection resolver instance.
1659 *
1660 * @return \FluentBooking\Framework\Database\ConnectionResolverInterface
1661 */
1662 public static function getConnectionResolver()
1663 {
1664 return static::$resolver;
1665 }
1666
1667 /**
1668 * Set the connection resolver instance.
1669 *
1670 * @param \FluentBooking\Framework\Database\ConnectionResolverInterface $resolver
1671 * @return void
1672 */
1673 public static function setConnectionResolver(Resolver $resolver)
1674 {
1675 static::$resolver = $resolver;
1676 }
1677
1678 /**
1679 * Unset the connection resolver for models.
1680 *
1681 * @return void
1682 */
1683 public static function unsetConnectionResolver()
1684 {
1685 static::$resolver = null;
1686 }
1687
1688 /**
1689 * Get the table associated with the model.
1690 *
1691 * @return string
1692 */
1693 public function getTable()
1694 {
1695 return $this->table ?? Str::snake(Str::pluralStudly(static::classBasename($this)));
1696 }
1697
1698 /**
1699 * Set the table associated with the model.
1700 *
1701 * @param string $table
1702 * @return $this
1703 */
1704 public function setTable($table)
1705 {
1706 $this->table = $table;
1707
1708 return $this;
1709 }
1710
1711 /**
1712 * Get the primary key for the model.
1713 *
1714 * @return string
1715 */
1716 public function getKeyName()
1717 {
1718 return $this->primaryKey;
1719 }
1720
1721 /**
1722 * Set the primary key for the model.
1723 *
1724 * @param string $key
1725 * @return $this
1726 */
1727 public function setKeyName($key)
1728 {
1729 $this->primaryKey = $key;
1730
1731 return $this;
1732 }
1733
1734 /**
1735 * Get the table qualified key name.
1736 *
1737 * @return string
1738 */
1739 public function getQualifiedKeyName()
1740 {
1741 return $this->qualifyColumn($this->getKeyName());
1742 }
1743
1744 /**
1745 * Get the auto-incrementing key type.
1746 *
1747 * @return string
1748 */
1749 public function getKeyType()
1750 {
1751 return $this->keyType;
1752 }
1753
1754 /**
1755 * Set the data type for the primary key.
1756 *
1757 * @param string $type
1758 * @return $this
1759 */
1760 public function setKeyType($type)
1761 {
1762 $this->keyType = $type;
1763
1764 return $this;
1765 }
1766
1767 /**
1768 * Get the value indicating whether the IDs are incrementing.
1769 *
1770 * @return bool
1771 */
1772 public function getIncrementing()
1773 {
1774 return $this->incrementing;
1775 }
1776
1777 /**
1778 * Set whether IDs are incrementing.
1779 *
1780 * @param bool $value
1781 * @return $this
1782 */
1783 public function setIncrementing($value)
1784 {
1785 $this->incrementing = $value;
1786
1787 return $this;
1788 }
1789
1790 /**
1791 * Get the value of the model's primary key.
1792 *
1793 * @return mixed
1794 */
1795 public function getKey()
1796 {
1797 return $this->getAttribute($this->getKeyName());
1798 }
1799
1800 /**
1801 * Get the queueable identity for the entity.
1802 *
1803 * @return mixed
1804 */
1805 public function getQueueableId()
1806 {
1807 return $this->getKey();
1808 }
1809
1810 /**
1811 * Get the queueable relationships for the entity.
1812 *
1813 * @return array
1814 */
1815 public function getQueueableRelations()
1816 {
1817 $relations = [];
1818
1819 foreach ($this->getRelations() as $key => $relation) {
1820 if (! method_exists($this, $key)) {
1821 continue;
1822 }
1823
1824 $relations[] = $key;
1825 }
1826
1827 return array_unique($relations);
1828 }
1829
1830 /**
1831 * Get the queueable connection for the entity.
1832 *
1833 * @return string|null
1834 */
1835 public function getQueueableConnection()
1836 {
1837 return $this->getConnectionName();
1838 }
1839
1840 /**
1841 * Get the value of the model's route key.
1842 *
1843 * @return mixed
1844 */
1845 public function getRouteKey()
1846 {
1847 return $this->getAttribute($this->getRouteKeyName());
1848 }
1849
1850 /**
1851 * Get the route key for the model.
1852 *
1853 * @return string
1854 */
1855 public function getRouteKeyName()
1856 {
1857 return $this->getKeyName();
1858 }
1859
1860 /**
1861 * Retrieve the model for a bound value.
1862 *
1863 * @param mixed $value
1864 * @param string|null $field
1865 * @return \FluentBooking\Framework\Database\Orm\Model|null
1866 */
1867 public function resolveRouteBinding($value, $field = null)
1868 {
1869 return $this->resolveRouteBindingQuery($this, $value, $field)->first();
1870 }
1871
1872 /**
1873 * Retrieve the model for a bound value.
1874 *
1875 * @param mixed $value
1876 * @param string|null $field
1877 * @return \FluentBooking\Framework\Database\Orm\Model|null
1878 */
1879 public function resolveSoftDeletableRouteBinding($value, $field = null)
1880 {
1881 return $this->resolveRouteBindingQuery($this, $value, $field)->withTrashed()->first();
1882 }
1883
1884 /**
1885 * Retrieve the child model for a bound value.
1886 *
1887 * @param string $childType
1888 * @param mixed $value
1889 * @param string|null $field
1890 * @return \FluentBooking\Framework\Database\Orm\Model|null
1891 */
1892 public function resolveChildRouteBinding($childType, $value, $field)
1893 {
1894 return $this->resolveChildRouteBindingQuery($childType, $value, $field)->first();
1895 }
1896
1897 /**
1898 * Retrieve the child model for a bound value.
1899 *
1900 * @param string $childType
1901 * @param mixed $value
1902 * @param string|null $field
1903 * @return \FluentBooking\Framework\Database\Orm\Model|null
1904 */
1905 public function resolveSoftDeletableChildRouteBinding($childType, $value, $field)
1906 {
1907 return $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->first();
1908 }
1909
1910 /**
1911 * Retrieve the child model query for a bound value.
1912 *
1913 * @param string $childType
1914 * @param mixed $value
1915 * @param string|null $field
1916 * @return \FluentBooking\Framework\Database\Orm\Relations\Relation
1917 */
1918 protected function resolveChildRouteBindingQuery($childType, $value, $field)
1919 {
1920 $relationship = $this->{Str::plural(Str::camel($childType))}();
1921
1922 $field = $field ?: $relationship->getRelated()->getRouteKeyName();
1923
1924 if ($relationship instanceof HasManyThrough ||
1925 $relationship instanceof BelongsToMany) {
1926 $field = $relationship->getRelated()->getTable().'.'.$field;
1927 }
1928
1929 return $relationship instanceof Model
1930 ? $relationship->resolveRouteBindingQuery($relationship, $value, $field)
1931 : $relationship->getRelated()->resolveRouteBindingQuery($relationship, $value, $field);
1932 }
1933
1934 /**
1935 * Retrieve the model for a bound value.
1936 *
1937 * @param \FluentBooking\Framework\Database\Orm\Model|\FluentBooking\Framework\Database\Orm\Relations\Relation $query
1938 * @param mixed $value
1939 * @param string|null $field
1940 * @return \FluentBooking\Framework\Database\Orm\Relations\Relation
1941 */
1942 public function resolveRouteBindingQuery($query, $value, $field = null)
1943 {
1944 return $query->where($field ?? $this->getRouteKeyName(), $value);
1945 }
1946
1947 /**
1948 * Get the default foreign key name for the model.
1949 *
1950 * @return string
1951 */
1952 public function getForeignKey()
1953 {
1954 return Str::snake(static::classBasename($this)).'_'.$this->getKeyName();
1955 }
1956
1957 /**
1958 * Get the number of models to return per page.
1959 *
1960 * @return int
1961 */
1962 public function getPerPage()
1963 {
1964 return $this->perPage;
1965 }
1966
1967 /**
1968 * Set the number of models to return per page.
1969 *
1970 * @param int $perPage
1971 * @return $this
1972 */
1973 public function setPerPage($perPage)
1974 {
1975 $this->perPage = $perPage;
1976
1977 return $this;
1978 }
1979
1980 /**
1981 * Determine if lazy loading is disabled.
1982 *
1983 * @return bool
1984 */
1985 public static function preventsLazyLoading()
1986 {
1987 return static::$modelsShouldPreventLazyLoading;
1988 }
1989
1990 /**
1991 * Dynamically retrieve attributes on the model.
1992 *
1993 * @param string $key
1994 * @return mixed
1995 */
1996 public function __get($key)
1997 {
1998 return $this->getAttribute($key);
1999 }
2000
2001 /**
2002 * Dynamically set attributes on the model.
2003 *
2004 * @param string $key
2005 * @param mixed $value
2006 * @return void
2007 */
2008 public function __set($key, $value)
2009 {
2010 $this->setAttribute($key, $value);
2011 }
2012
2013 /**
2014 * Determine if the given attribute exists.
2015 *
2016 * @param mixed $offset
2017 * @return bool
2018 */
2019 #[\ReturnTypeWillChange]
2020 public function offsetExists($offset)
2021 {
2022 return ! is_null($this->getAttribute($offset));
2023 }
2024
2025 /**
2026 * Get the value for a given offset.
2027 *
2028 * @param mixed $offset
2029 * @return mixed
2030 */
2031 #[\ReturnTypeWillChange]
2032 public function offsetGet($offset)
2033 {
2034 return $this->getAttribute($offset);
2035 }
2036
2037 /**
2038 * Set the value for a given offset.
2039 *
2040 * @param mixed $offset
2041 * @param mixed $value
2042 * @return void
2043 */
2044 #[\ReturnTypeWillChange]
2045 public function offsetSet($offset, $value)
2046 {
2047 $this->setAttribute($offset, $value);
2048 }
2049
2050 /**
2051 * Unset the value for a given offset.
2052 *
2053 * @param mixed $offset
2054 * @return void
2055 */
2056 #[\ReturnTypeWillChange]
2057 public function offsetUnset($offset)
2058 {
2059 unset($this->attributes[$offset], $this->relations[$offset]);
2060 }
2061
2062 /**
2063 * Determine if an attribute or relation exists on the model.
2064 *
2065 * @param string $key
2066 * @return bool
2067 */
2068 public function __isset($key)
2069 {
2070 return $this->offsetExists($key);
2071 }
2072
2073 /**
2074 * Unset an attribute on the model.
2075 *
2076 * @param string $key
2077 * @return void
2078 */
2079 public function __unset($key)
2080 {
2081 $this->offsetUnset($key);
2082 }
2083
2084 /**
2085 * Handle dynamic method calls into the model.
2086 *
2087 * @param string $method
2088 * @param array $parameters
2089 * @return mixed
2090 */
2091 public function __call($method, $parameters)
2092 {
2093 if (in_array($method, ['increment', 'decrement'])) {
2094 return $this->$method(...$parameters);
2095 }
2096
2097 if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) {
2098 return $resolver($this);
2099 }
2100
2101 return $this->forwardCallTo($this->newQuery(), $method, $parameters);
2102 }
2103
2104 /**
2105 * Handle dynamic static method calls into the model.
2106 *
2107 * @param string $method
2108 * @param array $parameters
2109 * @return mixed
2110 */
2111 public static function __callStatic($method, $parameters)
2112 {
2113 return (new static)->$method(...$parameters);
2114 }
2115
2116 /**
2117 * Convert the model to its string representation.
2118 *
2119 * @return string
2120 */
2121 public function __toString()
2122 {
2123 return $this->escapeWhenCastingToString
2124 ? esc_html($this->toJson())
2125 : $this->toJson();
2126 }
2127
2128 /**
2129 * Indicate that the object's string representation should be escaped when __toString is invoked.
2130 *
2131 * @param bool $escape
2132 * @return self
2133 */
2134 public function escapeWhenCastingToString($escape = true)
2135 {
2136 $this->escapeWhenCastingToString = $escape;
2137
2138 return $this;
2139 }
2140
2141 /**
2142 * Prepare the object for serialization.
2143 *
2144 * @return array
2145 */
2146 public function __sleep()
2147 {
2148 $this->mergeAttributesFromCachedCasts();
2149
2150 $this->classCastCache = [];
2151 $this->attributeCastCache = [];
2152
2153 return array_keys(get_object_vars($this));
2154 }
2155
2156 /**
2157 * When a model is being unserialized, check if it needs to be booted.
2158 *
2159 * @return void
2160 */
2161 public function __wakeup()
2162 {
2163 $this->bootIfNotBooted();
2164
2165 $this->initializeTraits();
2166 }
2167 }
2168