PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.40
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.40
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Database / Orm / Model.php

Model.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.40, at vendor/wpfluent/framework/src/WPFluent/Database/Orm/Model.php

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