暂存
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||
use Illuminate\Database\Eloquent\Attributes\Initialize;
|
||||
use Illuminate\Database\Eloquent\Attributes\Unguarded;
|
||||
|
||||
trait GuardsAttributes
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $guarded = ['*'];
|
||||
|
||||
/**
|
||||
* Indicates if all mass assignment is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $unguarded = false;
|
||||
|
||||
/**
|
||||
* The actual columns that exist on the database and can be guarded.
|
||||
*
|
||||
* @var array<class-string,list<string>>
|
||||
*/
|
||||
protected static $guardableColumns = [];
|
||||
|
||||
/**
|
||||
* Initialize the GuardsAttributes trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[Initialize]
|
||||
public function initializeGuardsAttributes()
|
||||
{
|
||||
$this->mergeFillable(static::resolveClassAttribute(Fillable::class, 'columns') ?? []);
|
||||
|
||||
if ($this->guarded === ['*']) {
|
||||
if (static::resolveClassAttribute(Unguarded::class) !== null) {
|
||||
$this->guarded = [];
|
||||
} else {
|
||||
$this->guarded = static::resolveClassAttribute(Guarded::class, 'columns') ?? ['*'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fillable attributes for the model.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getFillable()
|
||||
{
|
||||
return $this->fillable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fillable attributes for the model.
|
||||
*
|
||||
* @param array<string> $fillable
|
||||
* @return $this
|
||||
*/
|
||||
public function fillable(array $fillable)
|
||||
{
|
||||
$this->fillable = $fillable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new fillable attributes with existing fillable attributes on the model.
|
||||
*
|
||||
* @param array<string> $fillable
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeFillable(array $fillable)
|
||||
{
|
||||
$this->fillable = array_values(array_unique(array_merge($this->fillable, $fillable)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the guarded attributes for the model.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getGuarded()
|
||||
{
|
||||
return self::$unguarded === true
|
||||
? []
|
||||
: $this->guarded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the guarded attributes for the model.
|
||||
*
|
||||
* @param array<string> $guarded
|
||||
* @return $this
|
||||
*/
|
||||
public function guard(array $guarded)
|
||||
{
|
||||
$this->guarded = $guarded;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new guarded attributes with existing guarded attributes on the model.
|
||||
*
|
||||
* @param array<string> $guarded
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeGuarded(array $guarded)
|
||||
{
|
||||
$this->guarded = array_values(array_unique(array_merge($this->guarded, $guarded)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all mass assignable restrictions.
|
||||
*
|
||||
* @param bool $state
|
||||
* @return void
|
||||
*/
|
||||
public static function unguard($state = true)
|
||||
{
|
||||
static::$unguarded = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the mass assignment restrictions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reguard()
|
||||
{
|
||||
static::$unguarded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current state is "unguarded".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUnguarded()
|
||||
{
|
||||
return static::$unguarded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given callable while being unguarded.
|
||||
*
|
||||
* @template TReturn
|
||||
*
|
||||
* @param callable(): TReturn $callback
|
||||
* @return TReturn
|
||||
*/
|
||||
public static function unguarded(callable $callback)
|
||||
{
|
||||
if (static::$unguarded) {
|
||||
return $callback();
|
||||
}
|
||||
|
||||
static::unguard();
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::reguard();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given attribute may be mass assigned.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function isFillable($key)
|
||||
{
|
||||
if (static::$unguarded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the key is in the "fillable" array, we can of course assume that it's
|
||||
// a fillable attribute. Otherwise, we will check the guarded array when
|
||||
// we need to determine if the attribute is black-listed on the model.
|
||||
if (in_array($key, $this->getFillable())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the attribute is explicitly listed in the "guarded" array then we can
|
||||
// return false immediately. This means this attribute is definitely not
|
||||
// fillable and there is no point in going any further in this method.
|
||||
if ($this->isGuarded($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return empty($this->getFillable()) &&
|
||||
! str_contains($key, '.') &&
|
||||
! str_starts_with($key, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key is guarded.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function isGuarded($key)
|
||||
{
|
||||
if (empty($this->getGuarded())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getGuarded() == ['*'] ||
|
||||
! empty(preg_grep('/^'.preg_quote($key, '/').'$/i', $this->getGuarded())) ||
|
||||
! $this->isGuardableColumn($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given column is a valid, guardable column.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isGuardableColumn($key)
|
||||
{
|
||||
if ($this->hasSetMutator($key) || $this->hasAttributeSetMutator($key) || $this->isClassCastable($key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! isset(static::$guardableColumns[get_class($this)])) {
|
||||
$columns = $this->getConnection()
|
||||
->getSchemaBuilder()
|
||||
->getColumnListing($this->getTable());
|
||||
|
||||
if (empty($columns)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static::$guardableColumns[get_class($this)] = $columns;
|
||||
}
|
||||
|
||||
return in_array($key, static::$guardableColumns[get_class($this)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is totally guarded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function totallyGuarded()
|
||||
{
|
||||
return count($this->getFillable()) === 0 && $this->getGuarded() == ['*'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fillable attributes of a given array.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function fillableFromArray(array $attributes)
|
||||
{
|
||||
if (count($this->getFillable()) > 0 && ! static::$unguarded) {
|
||||
return array_intersect_key($attributes, array_flip($this->getFillable()));
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Events\NullDispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
|
||||
trait HasEvents
|
||||
{
|
||||
/**
|
||||
* The event map for the model.
|
||||
*
|
||||
* Allows for object-based events for native Eloquent events.
|
||||
*
|
||||
* @var array<string, class-string>
|
||||
*/
|
||||
protected $dispatchesEvents = [];
|
||||
|
||||
/**
|
||||
* User exposed observable events.
|
||||
*
|
||||
* These are extra user-defined events observers may subscribe to.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $observables = [];
|
||||
|
||||
/**
|
||||
* Boot the has event trait for a model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootHasEvents()
|
||||
{
|
||||
static::whenBooted(fn () => static::observe(static::resolveObserveAttributes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the observe class names from the attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function resolveObserveAttributes()
|
||||
{
|
||||
$reflectionClass = new ReflectionClass(static::class);
|
||||
|
||||
$isEloquentGrandchild = is_subclass_of(static::class, Model::class)
|
||||
&& get_parent_class(static::class) !== Model::class;
|
||||
|
||||
return (new Collection($reflectionClass->getAttributes(ObservedBy::class)))
|
||||
->map(fn ($attribute) => $attribute->getArguments())
|
||||
->flatten()
|
||||
->when($isEloquentGrandchild, function (Collection $attributes) {
|
||||
return (new Collection(get_parent_class(static::class)::resolveObserveAttributes()))
|
||||
->merge($attributes);
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register observers with the model.
|
||||
*
|
||||
* @param object|string[]|string $classes
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function observe($classes)
|
||||
{
|
||||
$instance = new static;
|
||||
|
||||
foreach (Arr::wrap($classes) as $class) {
|
||||
$instance->registerObserver($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single observer with the model.
|
||||
*
|
||||
* @param object|string $class
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function registerObserver($class)
|
||||
{
|
||||
$className = $this->resolveObserverClassName($class);
|
||||
|
||||
// When registering a model observer, we will spin through the possible events
|
||||
// and determine if this observer has that method. If it does, we will hook
|
||||
// it into the model's event system, making it convenient to watch these.
|
||||
foreach ($this->getObservableEvents() as $event) {
|
||||
if (method_exists($class, $event)) {
|
||||
static::registerModelEvent($event, $className.'@'.$event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the observer's class name from an object or string.
|
||||
*
|
||||
* @param object|string $class
|
||||
* @return class-string
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function resolveObserverClassName($class)
|
||||
{
|
||||
if (is_object($class)) {
|
||||
return get_class($class);
|
||||
}
|
||||
|
||||
if (class_exists($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unable to find observer: '.$class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the observable event names.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getObservableEvents()
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
'retrieved', 'creating', 'created', 'updating', 'updated',
|
||||
'saving', 'saved', 'restoring', 'restored', 'replicating',
|
||||
'trashed', 'deleting', 'deleted', 'forceDeleting', 'forceDeleted',
|
||||
],
|
||||
$this->observables
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the observable event names.
|
||||
*
|
||||
* @param string[] $observables
|
||||
* @return $this
|
||||
*/
|
||||
public function setObservableEvents(array $observables)
|
||||
{
|
||||
$this->observables = $observables;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observable event name.
|
||||
*
|
||||
* @param string|string[] $observables
|
||||
* @return void
|
||||
*/
|
||||
public function addObservableEvents($observables)
|
||||
{
|
||||
$this->observables = array_unique(array_merge(
|
||||
$this->observables, is_array($observables) ? $observables : func_get_args()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an observable event name.
|
||||
*
|
||||
* @param string|string[] $observables
|
||||
* @return void
|
||||
*/
|
||||
public function removeObservableEvents($observables)
|
||||
{
|
||||
$this->observables = array_diff(
|
||||
$this->observables, is_array($observables) ? $observables : func_get_args()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a model event with the dispatcher.
|
||||
*
|
||||
* @param string $event
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
protected static function registerModelEvent($event, $callback)
|
||||
{
|
||||
if (isset(static::$dispatcher)) {
|
||||
$name = static::class;
|
||||
|
||||
static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the given event for the model.
|
||||
*
|
||||
* @param string $event
|
||||
* @param bool $halt
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fireModelEvent($event, $halt = true)
|
||||
{
|
||||
if (! isset(static::$dispatcher)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// First, we will get the proper method to call on the event dispatcher, and then we
|
||||
// will attempt to fire a custom, object based event for the given event. If that
|
||||
// returns a result we can return that result, or we'll call the string events.
|
||||
$method = $halt ? 'until' : 'dispatch';
|
||||
|
||||
$result = $this->filterModelEventResults(
|
||||
$this->fireCustomModelEvent($event, $method)
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! empty($result) ? $result : static::$dispatcher->{$method}(
|
||||
"eloquent.{$event}: ".static::class, $this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a custom model event for the given event.
|
||||
*
|
||||
* @param string $event
|
||||
* @param 'until'|'dispatch' $method
|
||||
* @return array|null|void
|
||||
*/
|
||||
protected function fireCustomModelEvent($event, $method)
|
||||
{
|
||||
if (! isset($this->dispatchesEvents[$event])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));
|
||||
|
||||
if (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the model event results.
|
||||
*
|
||||
* @param mixed $result
|
||||
* @return mixed
|
||||
*/
|
||||
protected function filterModelEventResults($result)
|
||||
{
|
||||
if (is_array($result)) {
|
||||
$result = array_filter($result, function ($response) {
|
||||
return ! is_null($response);
|
||||
});
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a retrieved model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function retrieved($callback)
|
||||
{
|
||||
static::registerModelEvent('retrieved', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a saving model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function saving($callback)
|
||||
{
|
||||
static::registerModelEvent('saving', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a saved model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function saved($callback)
|
||||
{
|
||||
static::registerModelEvent('saved', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an updating model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function updating($callback)
|
||||
{
|
||||
static::registerModelEvent('updating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an updated model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function updated($callback)
|
||||
{
|
||||
static::registerModelEvent('updated', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a creating model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function creating($callback)
|
||||
{
|
||||
static::registerModelEvent('creating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a created model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function created($callback)
|
||||
{
|
||||
static::registerModelEvent('created', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a replicating model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function replicating($callback)
|
||||
{
|
||||
static::registerModelEvent('replicating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a deleting model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function deleting($callback)
|
||||
{
|
||||
static::registerModelEvent('deleting', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a deleted model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function deleted($callback)
|
||||
{
|
||||
static::registerModelEvent('deleted', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all the event listeners for the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function flushEventListeners()
|
||||
{
|
||||
if (! isset(static::$dispatcher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = new static;
|
||||
|
||||
foreach ($instance->getObservableEvents() as $event) {
|
||||
static::$dispatcher->forget("eloquent.{$event}: ".static::class);
|
||||
}
|
||||
|
||||
foreach ($instance->dispatchesEvents as $event) {
|
||||
static::$dispatcher->forget($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event map for the model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dispatchesEvents()
|
||||
{
|
||||
return $this->dispatchesEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event dispatcher instance.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Events\Dispatcher|null
|
||||
*/
|
||||
public static function getEventDispatcher()
|
||||
{
|
||||
return static::$dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event dispatcher instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return void
|
||||
*/
|
||||
public static function setEventDispatcher(Dispatcher $dispatcher)
|
||||
{
|
||||
static::$dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the event dispatcher for models.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unsetEventDispatcher()
|
||||
{
|
||||
static::$dispatcher = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback without firing any model events for any model type.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutEvents(callable $callback)
|
||||
{
|
||||
$dispatcher = static::getEventDispatcher();
|
||||
|
||||
if ($dispatcher) {
|
||||
static::setEventDispatcher(new NullDispatcher($dispatcher));
|
||||
}
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
if ($dispatcher) {
|
||||
static::setEventDispatcher($dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
|
||||
trait HasGlobalScopes
|
||||
{
|
||||
/**
|
||||
* Boot the has global scopes trait for a model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootHasGlobalScopes()
|
||||
{
|
||||
static::addGlobalScopes(static::resolveGlobalScopeAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the global scope class names from the attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function resolveGlobalScopeAttributes()
|
||||
{
|
||||
$reflectionClass = new ReflectionClass(static::class);
|
||||
|
||||
$attributes = (new Collection($reflectionClass->getAttributes(ScopedBy::class, ReflectionAttribute::IS_INSTANCEOF)));
|
||||
|
||||
foreach ($reflectionClass->getTraits() as $trait) {
|
||||
$attributes->push(...$trait->getAttributes(ScopedBy::class, ReflectionAttribute::IS_INSTANCEOF));
|
||||
}
|
||||
|
||||
$isEloquentGrandchild = is_subclass_of(static::class, Model::class)
|
||||
&& get_parent_class(static::class) !== Model::class;
|
||||
|
||||
return $attributes->map(fn ($attribute) => $attribute->getArguments())
|
||||
->flatten()
|
||||
->when($isEloquentGrandchild, function (Collection $attributes) {
|
||||
return (new Collection(get_parent_class(static::class)::resolveGlobalScopeAttributes()))
|
||||
->merge($attributes);
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new global scope on the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder<static>): mixed)|string $scope
|
||||
* @param \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder<static>): mixed)|null $implementation
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function addGlobalScope($scope, $implementation = null)
|
||||
{
|
||||
if (is_string($scope) && ($implementation instanceof Closure || $implementation instanceof Scope)) {
|
||||
return static::$globalScopes[static::class][$scope] = $implementation;
|
||||
} elseif ($scope instanceof Closure) {
|
||||
return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope;
|
||||
} elseif ($scope instanceof Scope) {
|
||||
return static::$globalScopes[static::class][get_class($scope)] = $scope;
|
||||
} elseif (is_string($scope) && class_exists($scope) && is_subclass_of($scope, Scope::class)) {
|
||||
return static::$globalScopes[static::class][$scope] = new $scope;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope or be a class name of a class extending '.Scope::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple global scopes on the model.
|
||||
*
|
||||
* @param array $scopes
|
||||
* @return void
|
||||
*/
|
||||
public static function addGlobalScopes(array $scopes)
|
||||
{
|
||||
foreach ($scopes as $key => $scope) {
|
||||
if (is_string($key)) {
|
||||
static::addGlobalScope($key, $scope);
|
||||
} else {
|
||||
static::addGlobalScope($scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a model has a global scope.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|string $scope
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasGlobalScope($scope)
|
||||
{
|
||||
return ! is_null(static::getGlobalScope($scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a global scope registered with the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|string $scope
|
||||
* @return \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder<static>): mixed)|null
|
||||
*/
|
||||
public static function getGlobalScope($scope)
|
||||
{
|
||||
if (is_string($scope)) {
|
||||
return Arr::get(static::$globalScopes, static::class.'.'.$scope);
|
||||
}
|
||||
|
||||
return Arr::get(
|
||||
static::$globalScopes, static::class.'.'.get_class($scope)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the global scopes that are currently registered.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAllGlobalScopes()
|
||||
{
|
||||
return static::$globalScopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current global scopes.
|
||||
*
|
||||
* @param array $scopes
|
||||
* @return void
|
||||
*/
|
||||
public static function setAllGlobalScopes($scopes)
|
||||
{
|
||||
static::$globalScopes = $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global scopes for this class instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGlobalScopes()
|
||||
{
|
||||
return Arr::get(static::$globalScopes, static::class, []);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Initialize;
|
||||
use Illuminate\Database\Eloquent\Attributes\Table;
|
||||
use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
trait HasTimestamps
|
||||
{
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* The list of models classes that have timestamps temporarily disabled.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $ignoreTimestampsOn = [];
|
||||
|
||||
/**
|
||||
* Initialize the HasTimestamps trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[Initialize]
|
||||
public function initializeHasTimestamps()
|
||||
{
|
||||
if ($this->timestamps === true) {
|
||||
if (static::resolveClassAttribute(WithoutTimestamps::class) !== null) {
|
||||
$this->timestamps = false;
|
||||
} elseif (($table = static::resolveClassAttribute(Table::class)) && $table->timestamps !== null) {
|
||||
$this->timestamps = $table->timestamps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp.
|
||||
*
|
||||
* @param array|string|null $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function touch($attribute = null)
|
||||
{
|
||||
if ($attribute) {
|
||||
$time = $this->freshTimestamp();
|
||||
|
||||
foreach (Arr::wrap($attribute) as $column) {
|
||||
$this->{$column} = $time;
|
||||
}
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
if (! $this->usesTimestamps()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->updateTimestamps();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp without raising any events.
|
||||
*
|
||||
* @param array|string|null $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function touchQuietly($attribute = null)
|
||||
{
|
||||
return static::withoutEvents(fn () => $this->touch($attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the creation and update timestamps.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function updateTimestamps()
|
||||
{
|
||||
$time = $this->freshTimestamp();
|
||||
|
||||
$updatedAtColumn = $this->getUpdatedAtColumn();
|
||||
|
||||
if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) {
|
||||
$this->setUpdatedAt($time);
|
||||
}
|
||||
|
||||
$createdAtColumn = $this->getCreatedAtColumn();
|
||||
|
||||
if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) {
|
||||
$this->setCreatedAt($time);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the "created at" attribute.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setCreatedAt($value)
|
||||
{
|
||||
$this->{$this->getCreatedAtColumn()} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the "updated at" attribute.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setUpdatedAt($value)
|
||||
{
|
||||
$this->{$this->getUpdatedAtColumn()} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh timestamp for the model.
|
||||
*
|
||||
* @return \Illuminate\Support\Carbon
|
||||
*/
|
||||
public function freshTimestamp()
|
||||
{
|
||||
return Date::now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh timestamp for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function freshTimestampString()
|
||||
{
|
||||
return $this->fromDateTime($this->freshTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model uses timestamps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usesTimestamps()
|
||||
{
|
||||
return $this->timestamps && ! static::isIgnoringTimestamps($this::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "created at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCreatedAtColumn()
|
||||
{
|
||||
return static::CREATED_AT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "updated at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUpdatedAtColumn()
|
||||
{
|
||||
return static::UPDATED_AT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully-qualified "created at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedCreatedAtColumn()
|
||||
{
|
||||
$column = $this->getCreatedAtColumn();
|
||||
|
||||
return $column ? $this->qualifyColumn($column) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully-qualified "updated at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedUpdatedAtColumn()
|
||||
{
|
||||
$column = $this->getUpdatedAtColumn();
|
||||
|
||||
return $column ? $this->qualifyColumn($column) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable timestamps for the current class during the given callback scope.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutTimestamps(callable $callback)
|
||||
{
|
||||
return static::withoutTimestampsOn([static::class], $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable timestamps for the given model classes during the given callback scope.
|
||||
*
|
||||
* @param array $models
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutTimestampsOn($models, $callback)
|
||||
{
|
||||
static::$ignoreTimestampsOn = array_values(array_merge(static::$ignoreTimestampsOn, $models));
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
foreach ($models as $model) {
|
||||
if (($key = array_search($model, static::$ignoreTimestampsOn, true)) !== false) {
|
||||
unset(static::$ignoreTimestampsOn[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given model is ignoring timestamps / touches.
|
||||
*
|
||||
* @param string|null $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function isIgnoringTimestamps($class = null)
|
||||
{
|
||||
$class ??= static::class;
|
||||
|
||||
foreach (static::$ignoreTimestampsOn as $ignoredClass) {
|
||||
if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasUlids
|
||||
{
|
||||
use HasUniqueStringIds;
|
||||
|
||||
/**
|
||||
* Generate a new unique key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return strtolower((string) Str::ulid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if given key is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidUniqueId($value): bool
|
||||
{
|
||||
return Str::isUlid($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
trait HasUniqueIds
|
||||
{
|
||||
/**
|
||||
* Indicates if the model uses unique IDs.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $usesUniqueIds = false;
|
||||
|
||||
/**
|
||||
* Determine if the model uses unique IDs.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usesUniqueIds()
|
||||
{
|
||||
return $this->usesUniqueIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique keys for the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUniqueIds()
|
||||
{
|
||||
foreach ($this->uniqueIds() as $column) {
|
||||
if (empty($this->{$column})) {
|
||||
$this->{$column} = $this->newUniqueId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should receive a unique identifier.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function uniqueIds()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
trait HasUniqueStringIds
|
||||
{
|
||||
/**
|
||||
* Generate a new unique key for the model.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function newUniqueId();
|
||||
|
||||
/**
|
||||
* Determine if given key is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function isValidUniqueId($value): bool;
|
||||
|
||||
/**
|
||||
* Initialize the trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initializeHasUniqueStringIds()
|
||||
{
|
||||
$this->usesUniqueIds = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should receive a unique identifier.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function uniqueIds()
|
||||
{
|
||||
return $this->usesUniqueIds() ? [$this->getKeyName()] : parent::uniqueIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model for a bound value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $query
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Contracts\Database\Eloquent\Builder
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function resolveRouteBindingQuery($query, $value, $field = null)
|
||||
{
|
||||
if ($field && in_array($field, $this->uniqueIds()) && ! $this->isValidUniqueId($value)) {
|
||||
$this->handleInvalidUniqueId($value, $field);
|
||||
}
|
||||
|
||||
if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! $this->isValidUniqueId($value)) {
|
||||
$this->handleInvalidUniqueId($value, $field);
|
||||
}
|
||||
|
||||
return parent::resolveRouteBindingQuery($query, $value, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-incrementing key type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyType()
|
||||
{
|
||||
if (in_array($this->getKeyName(), $this->uniqueIds())) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
return parent::getKeyType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value indicating whether the IDs are incrementing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIncrementing()
|
||||
{
|
||||
if (in_array($this->getKeyName(), $this->uniqueIds())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::getIncrementing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception for the given invalid unique ID.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return never
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
protected function handleInvalidUniqueId($value, $field)
|
||||
{
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasUuids
|
||||
{
|
||||
use HasUniqueStringIds;
|
||||
|
||||
/**
|
||||
* Generate a new unique key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return (string) Str::uuid7();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if given key is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidUniqueId($value): bool
|
||||
{
|
||||
return Str::isUuid($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasVersion4Uuids
|
||||
{
|
||||
use HasUuids;
|
||||
|
||||
/**
|
||||
* Generate a new UUID (version 4) for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return (string) Str::orderedUuid();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Attributes\Initialize;
|
||||
use Illuminate\Database\Eloquent\Attributes\Visible;
|
||||
|
||||
trait HidesAttributes
|
||||
{
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be visible in serialization.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $visible = [];
|
||||
|
||||
/**
|
||||
* Initialize the HidesAttributes trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[Initialize]
|
||||
public function initializeHidesAttributes()
|
||||
{
|
||||
$this->mergeHidden(static::resolveClassAttribute(Hidden::class, 'columns') ?? []);
|
||||
$this->mergeVisible(static::resolveClassAttribute(Visible::class, 'columns') ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hidden attributes for the model.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getHidden()
|
||||
{
|
||||
return $this->hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hidden attributes for the model.
|
||||
*
|
||||
* @param array<string> $hidden
|
||||
* @return $this
|
||||
*/
|
||||
public function setHidden(array $hidden)
|
||||
{
|
||||
$this->hidden = $hidden;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new hidden attributes with existing hidden attributes on the model.
|
||||
*
|
||||
* @param array<string> $hidden
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeHidden(array $hidden)
|
||||
{
|
||||
$this->hidden = array_values(array_unique(array_merge($this->hidden, $hidden)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visible attributes for the model.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getVisible()
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visible attributes for the model.
|
||||
*
|
||||
* @param array<string> $visible
|
||||
* @return $this
|
||||
*/
|
||||
public function setVisible(array $visible)
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new visible attributes with existing visible attributes on the model.
|
||||
*
|
||||
* @param array<string> $visible
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeVisible(array $visible)
|
||||
{
|
||||
$this->visible = array_values(array_unique(array_merge($this->visible, $visible)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible.
|
||||
*
|
||||
* @param array<string>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisible($attributes)
|
||||
{
|
||||
$attributes = is_array($attributes) ? $attributes : func_get_args();
|
||||
|
||||
$this->hidden = array_diff($this->hidden, $attributes);
|
||||
|
||||
if (! empty($this->visible)) {
|
||||
$this->visible = array_values(array_unique(array_merge($this->visible, $attributes)));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible if the given truth test passes.
|
||||
*
|
||||
* @param bool|\Closure $condition
|
||||
* @param array<string>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisibleIf($condition, $attributes)
|
||||
{
|
||||
return value($condition, $this) ? $this->makeVisible($attributes) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden.
|
||||
*
|
||||
* @param array<string>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHidden($attributes)
|
||||
{
|
||||
$this->hidden = array_values(array_unique(array_merge(
|
||||
$this->hidden, is_array($attributes) ? $attributes : func_get_args()
|
||||
)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden if the given truth test passes.
|
||||
*
|
||||
* @param bool|\Closure $condition
|
||||
* @param array<string>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHiddenIf($condition, $attributes)
|
||||
{
|
||||
return value($condition, $this) ? $this->makeHidden($attributes) : $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Onceable;
|
||||
use WeakMap;
|
||||
|
||||
trait PreventsCircularRecursion
|
||||
{
|
||||
/**
|
||||
* The cache of objects processed to prevent infinite recursion.
|
||||
*
|
||||
* @var WeakMap<static, array<string, mixed>>
|
||||
*/
|
||||
protected static $recursionCache;
|
||||
|
||||
/**
|
||||
* Prevent a method from being called multiple times on the same object within the same call stack.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
protected function withoutRecursion($callback, $default = null)
|
||||
{
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
|
||||
|
||||
$onceable = Onceable::tryFromTrace($trace, $callback);
|
||||
|
||||
if (is_null($onceable)) {
|
||||
return call_user_func($callback);
|
||||
}
|
||||
|
||||
$stack = static::getRecursiveCallStack($this);
|
||||
|
||||
if (array_key_exists($onceable->hash, $stack)) {
|
||||
return is_callable($stack[$onceable->hash])
|
||||
? static::setRecursiveCallValue($this, $onceable->hash, call_user_func($stack[$onceable->hash]))
|
||||
: $stack[$onceable->hash];
|
||||
}
|
||||
|
||||
try {
|
||||
static::setRecursiveCallValue($this, $onceable->hash, $default);
|
||||
|
||||
return call_user_func($onceable->callable);
|
||||
} finally {
|
||||
static::clearRecursiveCallValue($this, $onceable->hash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry from the recursion cache for an object.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $hash
|
||||
*/
|
||||
protected static function clearRecursiveCallValue($object, string $hash)
|
||||
{
|
||||
if ($stack = Arr::except(static::getRecursiveCallStack($object), $hash)) {
|
||||
static::getRecursionCache()->offsetSet($object, $stack);
|
||||
} elseif (static::getRecursionCache()->offsetExists($object)) {
|
||||
static::getRecursionCache()->offsetUnset($object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stack of methods being called recursively for the current object.
|
||||
*
|
||||
* @param object $object
|
||||
* @return array
|
||||
*/
|
||||
protected static function getRecursiveCallStack($object): array
|
||||
{
|
||||
return static::getRecursionCache()->offsetExists($object)
|
||||
? static::getRecursionCache()->offsetGet($object)
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current recursion cache being used by the model.
|
||||
*
|
||||
* @return WeakMap
|
||||
*/
|
||||
protected static function getRecursionCache()
|
||||
{
|
||||
return static::$recursionCache ??= new WeakMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the recursion cache for the given object and method.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $hash
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function setRecursiveCallValue($object, string $hash, $value)
|
||||
{
|
||||
static::getRecursionCache()->offsetSet(
|
||||
$object,
|
||||
tap(static::getRecursiveCallStack($object), fn (&$stack) => $stack[$hash] = $value),
|
||||
);
|
||||
|
||||
return static::getRecursiveCallStack($object)[$hash];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\UseResource;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Str;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
|
||||
trait TransformsToResource
|
||||
{
|
||||
/**
|
||||
* Create a new resource object for the given resource.
|
||||
*
|
||||
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource>|null $resourceClass
|
||||
* @return \Illuminate\Http\Resources\Json\JsonResource
|
||||
*/
|
||||
public function toResource(?string $resourceClass = null): JsonResource
|
||||
{
|
||||
if ($resourceClass === null) {
|
||||
return $this->guessResource();
|
||||
}
|
||||
|
||||
return $resourceClass::make($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the resource class for the model.
|
||||
*
|
||||
* @return \Illuminate\Http\Resources\Json\JsonResource
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
protected function guessResource(): JsonResource
|
||||
{
|
||||
$resourceClass = $this->resolveResourceFromAttribute(static::class);
|
||||
|
||||
if ($resourceClass !== null && class_exists($resourceClass)) {
|
||||
return $resourceClass::make($this);
|
||||
}
|
||||
|
||||
foreach (static::guessResourceName() as $resourceClass) {
|
||||
if (is_string($resourceClass) && class_exists($resourceClass)) {
|
||||
return $resourceClass::make($this);
|
||||
}
|
||||
}
|
||||
|
||||
throw new LogicException(sprintf('Failed to find resource class for model [%s].', get_class($this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the resource class name for the model.
|
||||
*
|
||||
* @return array{class-string<\Illuminate\Http\Resources\Json\JsonResource>, class-string<\Illuminate\Http\Resources\Json\JsonResource>}
|
||||
*/
|
||||
public static function guessResourceName(): array
|
||||
{
|
||||
$modelClass = static::class;
|
||||
|
||||
if (! Str::contains($modelClass, '\\Models\\')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$relativeNamespace = Str::after($modelClass, '\\Models\\');
|
||||
|
||||
$relativeNamespace = Str::contains($relativeNamespace, '\\')
|
||||
? Str::before($relativeNamespace, '\\'.class_basename($modelClass))
|
||||
: '';
|
||||
|
||||
$potentialResource = sprintf(
|
||||
'%s\\Http\\Resources\\%s%s',
|
||||
Str::before($modelClass, '\\Models'),
|
||||
(string) $relativeNamespace !== '' ? $relativeNamespace.'\\' : '',
|
||||
class_basename($modelClass)
|
||||
);
|
||||
|
||||
return [$potentialResource.'Resource', $potentialResource];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource class from the class attribute.
|
||||
*
|
||||
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class
|
||||
* @return class-string<*>|null
|
||||
*/
|
||||
protected function resolveResourceFromAttribute(string $class): ?string
|
||||
{
|
||||
if (! class_exists($class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$attributes = (new ReflectionClass($class))->getAttributes(UseResource::class);
|
||||
|
||||
return $attributes !== []
|
||||
? $attributes[0]->newInstance()->class
|
||||
: null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user