PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.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.11.0, at vendor/wpfluent/framework/src/WPFluent/Database/Orm/Model.php

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