PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.6.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.6.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Database / Orm / Model.php

Model.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.6.0, at vendor/wpfluent/framework/src/WPFluent/Database/Orm/Model.php

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