init
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
trait GuardsAttributes
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array<string>|bool
|
||||
*/
|
||||
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<string>
|
||||
*/
|
||||
protected static $guardableColumns = [];
|
||||
|
||||
/**
|
||||
* 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 $this->guarded === false
|
||||
? []
|
||||
: $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.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
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)) {
|
||||
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 $attributes
|
||||
* @return array
|
||||
*/
|
||||
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
|
||||
*/
|
||||
protected $dispatchesEvents = [];
|
||||
|
||||
/**
|
||||
* User exposed observable events.
|
||||
*
|
||||
* These are extra user-defined events observers may subscribe to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $observables = [];
|
||||
|
||||
/**
|
||||
* Boot the has event trait for a model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootHasEvents()
|
||||
{
|
||||
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|array|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 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 array
|
||||
*/
|
||||
public function getObservableEvents()
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
'retrieved', 'creating', 'created', 'updating', 'updated',
|
||||
'saving', 'saved', 'restoring', 'restored', 'replicating',
|
||||
'deleting', 'deleted', 'forceDeleting', 'forceDeleted',
|
||||
],
|
||||
$this->observables
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the observable event names.
|
||||
*
|
||||
* @param array $observables
|
||||
* @return $this
|
||||
*/
|
||||
public function setObservableEvents(array $observables)
|
||||
{
|
||||
$this->observables = $observables;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observable event name.
|
||||
*
|
||||
* @param array|mixed $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 array|mixed $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 string $method
|
||||
* @return mixed|null
|
||||
*/
|
||||
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,139 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
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);
|
||||
|
||||
return (new Collection($reflectionClass->getAttributes(ScopedBy::class)))
|
||||
->map(fn ($attribute) => $attribute->getArguments())
|
||||
->flatten()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new global scope on the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|\Closure|string $scope
|
||||
* @param \Illuminate\Database\Eloquent\Scope|\Closure|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|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, []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,997 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\ClassMorphViolationException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\PendingHasThroughRelationship;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasRelationships
|
||||
{
|
||||
/**
|
||||
* The loaded relationships for the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $relations = [];
|
||||
|
||||
/**
|
||||
* The relationships that should be touched on save.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $touches = [];
|
||||
|
||||
/**
|
||||
* The many to many relationship methods.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $manyMethods = [
|
||||
'belongsToMany', 'morphToMany', 'morphedByMany',
|
||||
];
|
||||
|
||||
/**
|
||||
* The relation resolver callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $relationResolvers = [];
|
||||
|
||||
/**
|
||||
* Get the dynamic relation resolver if defined or inherited, or return null.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $class
|
||||
* @param string $key
|
||||
* @return Closure|null
|
||||
*/
|
||||
public function relationResolver($class, $key)
|
||||
{
|
||||
if ($resolver = static::$relationResolvers[$class][$key] ?? null) {
|
||||
return $resolver;
|
||||
}
|
||||
|
||||
if ($parent = get_parent_class($class)) {
|
||||
return $this->relationResolver($parent, $key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a dynamic relation resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function resolveRelationUsing($name, Closure $callback)
|
||||
{
|
||||
static::$relationResolvers = array_replace_recursive(
|
||||
static::$relationResolvers,
|
||||
[static::class => [$name => $callback]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a one-to-one relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne<TRelatedModel, $this>
|
||||
*/
|
||||
public function hasOne($related, $foreignKey = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newHasOne($instance->newQuery(), $this, $instance->qualifyColumn($foreignKey), $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasOne relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
|
||||
{
|
||||
return new HasOne($query, $parent, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a has-one-through relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param class-string<TIntermediateModel> $through
|
||||
* @param string|null $firstKey
|
||||
* @param string|null $secondKey
|
||||
* @param string|null $localKey
|
||||
* @param string|null $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough<TRelatedModel, TIntermediateModel, $this>
|
||||
*/
|
||||
public function hasOneThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
|
||||
{
|
||||
$through = $this->newRelatedThroughInstance($through);
|
||||
|
||||
$firstKey = $firstKey ?: $this->getForeignKey();
|
||||
|
||||
$secondKey = $secondKey ?: $through->getForeignKey();
|
||||
|
||||
return $this->newHasOneThrough(
|
||||
$this->newRelatedInstance($related)->newQuery(), $this, $through,
|
||||
$firstKey, $secondKey, $localKey ?: $this->getKeyName(),
|
||||
$secondLocalKey ?: $through->getKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasOneThrough relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $farParent
|
||||
* @param TIntermediateModel $throughParent
|
||||
* @param string $firstKey
|
||||
* @param string $secondKey
|
||||
* @param string $localKey
|
||||
* @param string $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough<TRelatedModel, TIntermediateModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
|
||||
{
|
||||
return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic one-to-one relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string $name
|
||||
* @param string|null $type
|
||||
* @param string|null $id
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne<TRelatedModel, $this>
|
||||
*/
|
||||
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
[$type, $id] = $this->getMorphs($name, $type, $id);
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newMorphOne($instance->newQuery(), $this, $instance->qualifyColumn($type), $instance->qualifyColumn($id), $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphOne relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey)
|
||||
{
|
||||
return new MorphOne($query, $parent, $type, $id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an inverse one-to-one or many relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $ownerKey
|
||||
* @param string|null $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<TRelatedModel, $this>
|
||||
*/
|
||||
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
|
||||
{
|
||||
// If no relation name was given, we will use this debug backtrace to extract
|
||||
// the calling method's name and use that as the relationship name as most
|
||||
// of the time this will be what we desire to use for the relationships.
|
||||
if (is_null($relation)) {
|
||||
$relation = $this->guessBelongsToRelation();
|
||||
}
|
||||
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
// If no foreign key was supplied, we can use a backtrace to guess the proper
|
||||
// foreign key name by using the name of the relationship function, which
|
||||
// when combined with an "_id" should conventionally match the columns.
|
||||
if (is_null($foreignKey)) {
|
||||
$foreignKey = Str::snake($relation).'_'.$instance->getKeyName();
|
||||
}
|
||||
|
||||
// Once we have the foreign key names we'll just create a new Eloquent query
|
||||
// for the related models and return the relationship instance which will
|
||||
// actually be responsible for retrieving and hydrating every relation.
|
||||
$ownerKey = $ownerKey ?: $instance->getKeyName();
|
||||
|
||||
return $this->newBelongsTo(
|
||||
$instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new BelongsTo relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $child
|
||||
* @param string $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
|
||||
{
|
||||
return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse one-to-one or many relationship.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param string|null $type
|
||||
* @param string|null $id
|
||||
* @param string|null $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this>
|
||||
*/
|
||||
public function morphTo($name = null, $type = null, $id = null, $ownerKey = null)
|
||||
{
|
||||
// If no name is provided, we will use the backtrace to get the function name
|
||||
// since that is most likely the name of the polymorphic interface. We can
|
||||
// use that to get both the class and foreign key that will be utilized.
|
||||
$name = $name ?: $this->guessBelongsToRelation();
|
||||
|
||||
[$type, $id] = $this->getMorphs(
|
||||
Str::snake($name), $type, $id
|
||||
);
|
||||
|
||||
// If the type value is null it is probably safe to assume we're eager loading
|
||||
// the relationship. In this case we'll just pass in a dummy query where we
|
||||
// need to remove any eager loads that may already be defined on a model.
|
||||
return is_null($class = $this->getAttributeFromArray($type)) || $class === ''
|
||||
? $this->morphEagerTo($name, $type, $id, $ownerKey)
|
||||
: $this->morphInstanceTo($class, $name, $type, $id, $ownerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse one-to-one or many relationship.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string|null $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this>
|
||||
*/
|
||||
protected function morphEagerTo($name, $type, $id, $ownerKey)
|
||||
{
|
||||
return $this->newMorphTo(
|
||||
$this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse one-to-one or many relationship.
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string|null $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this>
|
||||
*/
|
||||
protected function morphInstanceTo($target, $name, $type, $id, $ownerKey)
|
||||
{
|
||||
$instance = $this->newRelatedInstance(
|
||||
static::getActualClassNameForMorph($target)
|
||||
);
|
||||
|
||||
return $this->newMorphTo(
|
||||
$instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphTo relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string $foreignKey
|
||||
* @param string|null $ownerKey
|
||||
* @param string $type
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
|
||||
{
|
||||
return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actual class name for a given morph class.
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
public static function getActualClassNameForMorph($class)
|
||||
{
|
||||
return Arr::get(Relation::morphMap() ?: [], $class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the "belongs to" relationship name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function guessBelongsToRelation()
|
||||
{
|
||||
[$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
|
||||
return $caller['function'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pending has-many-through or has-one-through relationship.
|
||||
*
|
||||
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param string|\Illuminate\Database\Eloquent\Relations\HasMany<TIntermediateModel, covariant $this>|\Illuminate\Database\Eloquent\Relations\HasOne<TIntermediateModel, covariant $this> $relationship
|
||||
* @return (
|
||||
* $relationship is string
|
||||
* ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship<\Illuminate\Database\Eloquent\Model, $this>
|
||||
* : (
|
||||
* $relationship is \Illuminate\Database\Eloquent\Relations\HasMany<TIntermediateModel, $this>
|
||||
* ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship<TIntermediateModel, $this, \Illuminate\Database\Eloquent\Relations\HasMany<TIntermediateModel, $this>>
|
||||
* : \Illuminate\Database\Eloquent\PendingHasThroughRelationship<TIntermediateModel, $this, \Illuminate\Database\Eloquent\Relations\HasOne<TIntermediateModel, $this>>
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function through($relationship)
|
||||
{
|
||||
if (is_string($relationship)) {
|
||||
$relationship = $this->{$relationship}();
|
||||
}
|
||||
|
||||
return new PendingHasThroughRelationship($this, $relationship);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a one-to-many relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<TRelatedModel, $this>
|
||||
*/
|
||||
public function hasMany($related, $foreignKey = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newHasMany(
|
||||
$instance->newQuery(), $this, $instance->qualifyColumn($foreignKey), $localKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasMany relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
|
||||
{
|
||||
return new HasMany($query, $parent, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a has-many-through relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param class-string<TIntermediateModel> $through
|
||||
* @param string|null $firstKey
|
||||
* @param string|null $secondKey
|
||||
* @param string|null $localKey
|
||||
* @param string|null $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<TRelatedModel, TIntermediateModel, $this>
|
||||
*/
|
||||
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
|
||||
{
|
||||
$through = $this->newRelatedThroughInstance($through);
|
||||
|
||||
$firstKey = $firstKey ?: $this->getForeignKey();
|
||||
|
||||
$secondKey = $secondKey ?: $through->getForeignKey();
|
||||
|
||||
return $this->newHasManyThrough(
|
||||
$this->newRelatedInstance($related)->newQuery(),
|
||||
$this,
|
||||
$through,
|
||||
$firstKey,
|
||||
$secondKey,
|
||||
$localKey ?: $this->getKeyName(),
|
||||
$secondLocalKey ?: $through->getKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasManyThrough relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $farParent
|
||||
* @param TIntermediateModel $throughParent
|
||||
* @param string $firstKey
|
||||
* @param string $secondKey
|
||||
* @param string $localKey
|
||||
* @param string $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<TRelatedModel, TIntermediateModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
|
||||
{
|
||||
return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic one-to-many relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string $name
|
||||
* @param string|null $type
|
||||
* @param string|null $id
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<TRelatedModel, $this>
|
||||
*/
|
||||
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
// Here we will gather up the morph type and ID for the relationship so that we
|
||||
// can properly query the intermediate table of a relation. Finally, we will
|
||||
// get the table and create the relationship instances for the developers.
|
||||
[$type, $id] = $this->getMorphs($name, $type, $id);
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newMorphMany($instance->newQuery(), $this, $instance->qualifyColumn($type), $instance->qualifyColumn($id), $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphMany relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
|
||||
{
|
||||
return new MorphMany($query, $parent, $type, $id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a many-to-many relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string|class-string<\Illuminate\Database\Eloquent\Model>|null $table
|
||||
* @param string|null $foreignPivotKey
|
||||
* @param string|null $relatedPivotKey
|
||||
* @param string|null $parentKey
|
||||
* @param string|null $relatedKey
|
||||
* @param string|null $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<TRelatedModel, $this>
|
||||
*/
|
||||
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
|
||||
$parentKey = null, $relatedKey = null, $relation = null)
|
||||
{
|
||||
// If no relationship name was passed, we will pull backtraces to get the
|
||||
// name of the calling function. We will use that function name as the
|
||||
// title of this relation since that is a great convention to apply.
|
||||
if (is_null($relation)) {
|
||||
$relation = $this->guessBelongsToManyRelation();
|
||||
}
|
||||
|
||||
// First, we'll need to determine the foreign key and "other key" for the
|
||||
// relationship. Once we have determined the keys we'll make the query
|
||||
// instances as well as the relationship instances we need for this.
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
|
||||
|
||||
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
|
||||
|
||||
// If no table name was provided, we can guess it by concatenating the two
|
||||
// models using underscores in alphabetical order. The two model names
|
||||
// are transformed to snake case from their default CamelCase also.
|
||||
if (is_null($table)) {
|
||||
$table = $this->joiningTable($related, $instance);
|
||||
}
|
||||
|
||||
return $this->newBelongsToMany(
|
||||
$instance->newQuery(), $this, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey ?: $this->getKeyName(),
|
||||
$relatedKey ?: $instance->getKeyName(), $relation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new BelongsToMany relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string|class-string<\Illuminate\Database\Eloquent\Model> $table
|
||||
* @param string $foreignPivotKey
|
||||
* @param string $relatedPivotKey
|
||||
* @param string $parentKey
|
||||
* @param string $relatedKey
|
||||
* @param string|null $relationName
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey,
|
||||
$parentKey, $relatedKey, $relationName = null)
|
||||
{
|
||||
return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic many-to-many relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string $name
|
||||
* @param string|null $table
|
||||
* @param string|null $foreignPivotKey
|
||||
* @param string|null $relatedPivotKey
|
||||
* @param string|null $parentKey
|
||||
* @param string|null $relatedKey
|
||||
* @param string|null $relation
|
||||
* @param bool $inverse
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany<TRelatedModel, $this>
|
||||
*/
|
||||
public function morphToMany($related, $name, $table = null, $foreignPivotKey = null,
|
||||
$relatedPivotKey = null, $parentKey = null,
|
||||
$relatedKey = null, $relation = null, $inverse = false)
|
||||
{
|
||||
$relation = $relation ?: $this->guessBelongsToManyRelation();
|
||||
|
||||
// First, we will need to determine the foreign key and "other key" for the
|
||||
// relationship. Once we have determined the keys we will make the query
|
||||
// instances, as well as the relationship instances we need for these.
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignPivotKey = $foreignPivotKey ?: $name.'_id';
|
||||
|
||||
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
|
||||
|
||||
// Now we're ready to create a new query builder for the related model and
|
||||
// the relationship instances for this relation. This relation will set
|
||||
// appropriate query constraints then entirely manage the hydrations.
|
||||
if (! $table) {
|
||||
$words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
$lastWord = array_pop($words);
|
||||
|
||||
$table = implode('', $words).Str::plural($lastWord);
|
||||
}
|
||||
|
||||
return $this->newMorphToMany(
|
||||
$instance->newQuery(), $this, $name, $table,
|
||||
$foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(),
|
||||
$relatedKey ?: $instance->getKeyName(), $relation, $inverse
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphToMany relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<TRelatedModel> $query
|
||||
* @param TDeclaringModel $parent
|
||||
* @param string $name
|
||||
* @param string $table
|
||||
* @param string $foreignPivotKey
|
||||
* @param string $relatedPivotKey
|
||||
* @param string $parentKey
|
||||
* @param string $relatedKey
|
||||
* @param string|null $relationName
|
||||
* @param bool $inverse
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey,
|
||||
$relationName = null, $inverse = false)
|
||||
{
|
||||
return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey,
|
||||
$relationName, $inverse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse many-to-many relationship.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $related
|
||||
* @param string $name
|
||||
* @param string|null $table
|
||||
* @param string|null $foreignPivotKey
|
||||
* @param string|null $relatedPivotKey
|
||||
* @param string|null $parentKey
|
||||
* @param string|null $relatedKey
|
||||
* @param string|null $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany<TRelatedModel, $this>
|
||||
*/
|
||||
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
|
||||
$relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null)
|
||||
{
|
||||
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
|
||||
|
||||
// For the inverse of the polymorphic many-to-many relations, we will change
|
||||
// the way we determine the foreign and other keys, as it is the opposite
|
||||
// of the morph-to-many method since we're figuring out these inverses.
|
||||
$relatedPivotKey = $relatedPivotKey ?: $name.'_id';
|
||||
|
||||
return $this->morphToMany(
|
||||
$related, $name, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey, $relation, true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationship name of the belongsToMany relationship.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function guessBelongsToManyRelation()
|
||||
{
|
||||
$caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
|
||||
return ! in_array(
|
||||
$trace['function'],
|
||||
array_merge(static::$manyMethods, ['guessBelongsToManyRelation'])
|
||||
);
|
||||
});
|
||||
|
||||
return ! is_null($caller) ? $caller['function'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the joining table name for a many-to-many relation.
|
||||
*
|
||||
* @param string $related
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $instance
|
||||
* @return string
|
||||
*/
|
||||
public function joiningTable($related, $instance = null)
|
||||
{
|
||||
// The joining table name, by convention, is simply the snake cased models
|
||||
// sorted alphabetically and concatenated with an underscore, so we can
|
||||
// just sort the models and join them together to get the table name.
|
||||
$segments = [
|
||||
$instance ? $instance->joiningTableSegment()
|
||||
: Str::snake(class_basename($related)),
|
||||
$this->joiningTableSegment(),
|
||||
];
|
||||
|
||||
// Now that we have the model names in an array we can just sort them and
|
||||
// use the implode function to join them together with an underscores,
|
||||
// which is typically used by convention within the database system.
|
||||
sort($segments);
|
||||
|
||||
return strtolower(implode('_', $segments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this model's half of the intermediate table name for belongsToMany relationships.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function joiningTableSegment()
|
||||
{
|
||||
return Str::snake(class_basename($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model touches a given relation.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return bool
|
||||
*/
|
||||
public function touches($relation)
|
||||
{
|
||||
return in_array($relation, $this->getTouchedRelations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch the owning relations of the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function touchOwners()
|
||||
{
|
||||
$this->withoutRecursion(function () {
|
||||
foreach ($this->getTouchedRelations() as $relation) {
|
||||
$this->$relation()->touch();
|
||||
|
||||
if ($this->$relation instanceof self) {
|
||||
$this->$relation->fireModelEvent('saved', false);
|
||||
|
||||
$this->$relation->touchOwners();
|
||||
} elseif ($this->$relation instanceof EloquentCollection) {
|
||||
$this->$relation->each->touchOwners();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the polymorphic relationship columns.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @return array
|
||||
*/
|
||||
protected function getMorphs($name, $type, $id)
|
||||
{
|
||||
return [$type ?: $name.'_type', $id ?: $name.'_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for polymorphic relations.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array(static::class, $morphMap)) {
|
||||
return array_search(static::class, $morphMap, true);
|
||||
}
|
||||
|
||||
if (static::class === Pivot::class) {
|
||||
return static::class;
|
||||
}
|
||||
|
||||
if (Relation::requiresMorphMap()) {
|
||||
throw new ClassMorphViolationException($this);
|
||||
}
|
||||
|
||||
return static::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance for a related model.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $class
|
||||
* @return TRelatedModel
|
||||
*/
|
||||
protected function newRelatedInstance($class)
|
||||
{
|
||||
return tap(new $class, function ($instance) {
|
||||
if (! $instance->getConnectionName()) {
|
||||
$instance->setConnection($this->connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance for a related "through" model.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param class-string<TRelatedModel> $class
|
||||
* @return TRelatedModel
|
||||
*/
|
||||
protected function newRelatedThroughInstance($class)
|
||||
{
|
||||
return new $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the loaded relations for the instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRelations()
|
||||
{
|
||||
return $this->relations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified relationship.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRelation($relation)
|
||||
{
|
||||
return $this->relations[$relation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given relation is loaded.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function relationLoaded($key)
|
||||
{
|
||||
return array_key_exists($key, $this->relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given relationship on the model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setRelation($relation, $value)
|
||||
{
|
||||
$this->relations[$relation] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a loaded relationship.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetRelation($relation)
|
||||
{
|
||||
unset($this->relations[$relation]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entire relations array on the model.
|
||||
*
|
||||
* @param array $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function setRelations(array $relations)
|
||||
{
|
||||
$this->relations = $relations;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the instance and unset all the loaded relations.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutRelations()
|
||||
{
|
||||
$model = clone $this;
|
||||
|
||||
return $model->unsetRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset all the loaded relations for the instance.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetRelations()
|
||||
{
|
||||
$this->relations = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationships that are touched on save.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTouchedRelations()
|
||||
{
|
||||
return $this->touches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relationships that are touched on save.
|
||||
*
|
||||
* @param array $touches
|
||||
* @return $this
|
||||
*/
|
||||
public function setTouchedRelations(array $touches)
|
||||
{
|
||||
$this->touches = $touches;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
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 = [];
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp.
|
||||
*
|
||||
* @param string|null $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function touch($attribute = null)
|
||||
{
|
||||
if ($attribute) {
|
||||
$this->$attribute = $this->freshTimestamp();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
if (! $this->usesTimestamps()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->updateTimestamps();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp without raising any events.
|
||||
*
|
||||
* @param 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()
|
||||
{
|
||||
return $this->qualifyColumn($this->getCreatedAtColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "updated at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedUpdatedAtColumn()
|
||||
{
|
||||
return $this->qualifyColumn($this->getUpdatedAtColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable timestamps for the current class during the given callback scope.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @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::orderedUuid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 HasVersion7Uuids
|
||||
{
|
||||
use HasUuids;
|
||||
|
||||
/**
|
||||
* Generate a new UUID (version 7) for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return (string) Str::uuid7();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
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 = [];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,975 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\Eloquent\RelationNotFoundException;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
/** @mixin \Illuminate\Database\Eloquent\Builder */
|
||||
trait QueriesRelationships
|
||||
{
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null)
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
if (str_contains($relation, '.')) {
|
||||
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
|
||||
}
|
||||
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if ($relation instanceof MorphTo) {
|
||||
return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback);
|
||||
}
|
||||
|
||||
// If we only need to check for the existence of the relation, then we can optimize
|
||||
// the subquery to only run a "where exists" clause instead of this full "count"
|
||||
// clause. This will make these queries run much faster compared with a count.
|
||||
$method = $this->canUseExistsForExistenceCheck($operator, $count)
|
||||
? 'getRelationExistenceQuery'
|
||||
: 'getRelationExistenceCountQuery';
|
||||
|
||||
$hasQuery = $relation->{$method}(
|
||||
$relation->getRelated()->newQueryWithoutRelationships(), $this
|
||||
);
|
||||
|
||||
// Next we will call any given callback as an "anonymous" scope so they can get the
|
||||
// proper logical grouping of the where clauses if needed by this Eloquent query
|
||||
// builder. Then, we will be ready to finalize and return this query instance.
|
||||
if ($callback) {
|
||||
$hasQuery->callScope($callback);
|
||||
}
|
||||
|
||||
return $this->addHasWhere(
|
||||
$hasQuery, $relation, $operator, $count, $boolean
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add nested relationship count / exists conditions to the query.
|
||||
*
|
||||
* Sets up recursive call to whereHas until we finish the nested relation.
|
||||
*
|
||||
* @param string $relations
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
|
||||
{
|
||||
$relations = explode('.', $relations);
|
||||
|
||||
$doesntHave = $operator === '<' && $count === 1;
|
||||
|
||||
if ($doesntHave) {
|
||||
$operator = '>=';
|
||||
$count = 1;
|
||||
}
|
||||
|
||||
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
|
||||
// In order to nest "has", we need to add count relation constraints on the
|
||||
// callback Closure. We'll do this by simply passing the Closure its own
|
||||
// reference to itself so it calls itself recursively on each segment.
|
||||
count($relations) > 1
|
||||
? $q->whereHas(array_shift($relations), $closure)
|
||||
: $q->has(array_shift($relations), $operator, $count, 'and', $callback);
|
||||
};
|
||||
|
||||
return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function orHas($relation, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->has($relation, $operator, $count, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function doesntHave($relation, $boolean = 'and', ?Closure $callback = null)
|
||||
{
|
||||
return $this->has($relation, '<', 1, $boolean, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function orDoesntHave($relation)
|
||||
{
|
||||
return $this->doesntHave($relation, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function whereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->has($relation, $operator, $count, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* Also load the relationship with same condition.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function withWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->whereHas(Str::before($relation, ':'), $callback, $operator, $count)
|
||||
->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->has($relation, $operator, $count, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function whereDoesntHave($relation, ?Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHave($relation, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereDoesntHave($relation, ?Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHave($relation, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null)
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
$types = (array) $types;
|
||||
|
||||
if ($types === ['*']) {
|
||||
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())
|
||||
->filter()
|
||||
->map(fn ($item) => enum_value($item))
|
||||
->all();
|
||||
}
|
||||
|
||||
if (empty($types)) {
|
||||
return $this->where(new Expression('0'), $operator, $count, $boolean);
|
||||
}
|
||||
|
||||
foreach ($types as &$type) {
|
||||
$type = Relation::getMorphedModel($type) ?? $type;
|
||||
}
|
||||
|
||||
return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) {
|
||||
foreach ($types as $type) {
|
||||
$query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) {
|
||||
$belongsTo = $this->getBelongsToRelation($relation, $type);
|
||||
|
||||
if ($callback) {
|
||||
$callback = function ($query) use ($callback, $type) {
|
||||
return $callback($query, $type);
|
||||
};
|
||||
}
|
||||
|
||||
$query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass())
|
||||
->whereHas($belongsTo, $callback, $operator, $count);
|
||||
});
|
||||
}
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BelongsTo relationship for a single polymorphic type.
|
||||
*
|
||||
* @template TRelatedModel of \Illuminate\Database\Eloquent\Model
|
||||
* @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, TDeclaringModel> $relation
|
||||
* @param class-string<TRelatedModel> $type
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<TRelatedModel, TDeclaringModel>
|
||||
*/
|
||||
protected function getBelongsToRelation(MorphTo $relation, $type)
|
||||
{
|
||||
$belongsTo = Relation::noConstraints(function () use ($relation, $type) {
|
||||
return $this->model->belongsTo(
|
||||
$type,
|
||||
$relation->getForeignKeyName(),
|
||||
$relation->getOwnerKeyName()
|
||||
);
|
||||
});
|
||||
|
||||
$belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());
|
||||
|
||||
return $belongsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function orHasMorph($relation, $types, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, $operator, $count, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function doesntHaveMorph($relation, $types, $boolean = 'and', ?Closure $callback = null)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @return $this
|
||||
*/
|
||||
public function orDoesntHaveMorph($relation, $types)
|
||||
{
|
||||
return $this->doesntHaveMorph($relation, $types, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function whereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function whereDoesntHaveMorph($relation, $types, ?Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHaveMorph($relation, $types, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereDoesntHaveMorph($relation, $types, ?Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHaveMorph($relation, $types, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic where clause to a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function whereRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereHas($relation, function ($query) use ($column, $operator, $value) {
|
||||
if ($column instanceof Closure) {
|
||||
$column($query);
|
||||
} else {
|
||||
$query->where($column, $operator, $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "or where" clause to a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) {
|
||||
if ($column instanceof Closure) {
|
||||
$column($query);
|
||||
} else {
|
||||
$query->where($column, $operator, $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic count / exists condition to a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function whereDoesntHaveRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereDoesntHave($relation, function ($query) use ($column, $operator, $value) {
|
||||
if ($column instanceof Closure) {
|
||||
$column($query);
|
||||
} else {
|
||||
$query->where($column, $operator, $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "or where" clause to a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>|string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereDoesntHaveRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereDoesntHave($relation, function ($query) use ($column, $operator, $value) {
|
||||
if ($column instanceof Closure) {
|
||||
$column($query);
|
||||
} else {
|
||||
$query->where($column, $operator, $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with a where clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with a doesn't have clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function whereMorphDoesntHaveRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereDoesntHaveMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with an "or doesn't have" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereMorphDoesntHaveRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereDoesntHaveMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a morph-to relationship condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string|null $model
|
||||
* @return $this
|
||||
*/
|
||||
public function whereMorphedTo($relation, $model, $boolean = 'and')
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if (is_null($model)) {
|
||||
return $this->whereNull($relation->qualifyColumn($relation->getMorphType()), $boolean);
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array($model, $morphMap)) {
|
||||
$model = array_search($model, $morphMap, true);
|
||||
}
|
||||
|
||||
return $this->where($relation->qualifyColumn($relation->getMorphType()), $model, null, $boolean);
|
||||
}
|
||||
|
||||
return $this->where(function ($query) use ($relation, $model) {
|
||||
$query->where($relation->qualifyColumn($relation->getMorphType()), $model->getMorphClass())
|
||||
->where($relation->qualifyColumn($relation->getForeignKeyName()), $model->getKey());
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a not morph-to relationship condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return $this
|
||||
*/
|
||||
public function whereNotMorphedTo($relation, $model, $boolean = 'and')
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array($model, $morphMap)) {
|
||||
$model = array_search($model, $morphMap, true);
|
||||
}
|
||||
|
||||
return $this->whereNot($relation->qualifyColumn($relation->getMorphType()), '<=>', $model, $boolean);
|
||||
}
|
||||
|
||||
return $this->whereNot(function ($query) use ($relation, $model) {
|
||||
$query->where($relation->qualifyColumn($relation->getMorphType()), '<=>', $model->getMorphClass())
|
||||
->where($relation->qualifyColumn($relation->getForeignKeyName()), '<=>', $model->getKey());
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a morph-to relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string|null $model
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereMorphedTo($relation, $model)
|
||||
{
|
||||
return $this->whereMorphedTo($relation, $model, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a not morph-to relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo<*, *>|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhereNotMorphedTo($relation, $model)
|
||||
{
|
||||
return $this->whereNotMorphedTo($relation, $model, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "belongs to" relationship where clause to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model> $related
|
||||
* @param string|null $relationshipName
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\RelationNotFoundException
|
||||
*/
|
||||
public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and')
|
||||
{
|
||||
if (! $related instanceof EloquentCollection) {
|
||||
$relatedCollection = $related->newCollection([$related]);
|
||||
} else {
|
||||
$relatedCollection = $related;
|
||||
|
||||
$related = $relatedCollection->first();
|
||||
}
|
||||
|
||||
if ($relatedCollection->isEmpty()) {
|
||||
throw new InvalidArgumentException('Collection given to whereBelongsTo method may not be empty.');
|
||||
}
|
||||
|
||||
if ($relationshipName === null) {
|
||||
$relationshipName = Str::camel(class_basename($related));
|
||||
}
|
||||
|
||||
try {
|
||||
$relationship = $this->model->{$relationshipName}();
|
||||
} catch (BadMethodCallException) {
|
||||
throw RelationNotFoundException::make($this->model, $relationshipName);
|
||||
}
|
||||
|
||||
if (! $relationship instanceof BelongsTo) {
|
||||
throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class);
|
||||
}
|
||||
|
||||
$this->whereIn(
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
$relatedCollection->pluck($relationship->getOwnerKeyName())->toArray(),
|
||||
$boolean,
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "BelongsTo" relationship with an "or where" clause to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $related
|
||||
* @param string|null $relationshipName
|
||||
* @return $this
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function orWhereBelongsTo($related, $relationshipName = null)
|
||||
{
|
||||
return $this->whereBelongsTo($related, $relationshipName, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include an aggregate value for a relationship.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
|
||||
* @param string $function
|
||||
* @return $this
|
||||
*/
|
||||
public function withAggregate($relations, $column, $function = null)
|
||||
{
|
||||
if (empty($relations)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_null($this->query->columns)) {
|
||||
$this->query->select([$this->query->from.'.*']);
|
||||
}
|
||||
|
||||
$relations = is_array($relations) ? $relations : [$relations];
|
||||
|
||||
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
|
||||
// First we will determine if the name has been aliased using an "as" clause on the name
|
||||
// and if it has we will extract the actual relationship name and the desired name of
|
||||
// the resulting column. This allows multiple aggregates on the same relationships.
|
||||
$segments = explode(' ', $name);
|
||||
|
||||
unset($alias);
|
||||
|
||||
if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
|
||||
[$name, $alias] = [$segments[0], $segments[2]];
|
||||
}
|
||||
|
||||
$relation = $this->getRelationWithoutConstraints($name);
|
||||
|
||||
if ($function) {
|
||||
if ($this->getQuery()->getGrammar()->isExpression($column)) {
|
||||
$aggregateColumn = $this->getQuery()->getGrammar()->getValue($column);
|
||||
} else {
|
||||
$hashedColumn = $this->getRelationHashedColumn($column, $relation);
|
||||
|
||||
$aggregateColumn = $this->getQuery()->getGrammar()->wrap(
|
||||
$column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
|
||||
);
|
||||
}
|
||||
|
||||
$expression = $function === 'exists' ? $aggregateColumn : sprintf('%s(%s)', $function, $aggregateColumn);
|
||||
} else {
|
||||
$expression = $this->getQuery()->getGrammar()->getValue($column);
|
||||
}
|
||||
|
||||
// Here, we will grab the relationship sub-query and prepare to add it to the main query
|
||||
// as a sub-select. First, we'll get the "has" query and use that to get the relation
|
||||
// sub-query. We'll format this relationship name and append this column if needed.
|
||||
$query = $relation->getRelationExistenceQuery(
|
||||
$relation->getRelated()->newQuery(), $this, new Expression($expression)
|
||||
)->setBindings([], 'select');
|
||||
|
||||
$query->callScope($constraints);
|
||||
|
||||
$query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
|
||||
|
||||
// If the query contains certain elements like orderings / more than one column selected
|
||||
// then we will remove those elements from the query so that it will execute properly
|
||||
// when given to the database. Otherwise, we may receive SQL errors or poor syntax.
|
||||
$query->orders = null;
|
||||
$query->setBindings([], 'order');
|
||||
|
||||
if (count($query->columns) > 1) {
|
||||
$query->columns = [$query->columns[0]];
|
||||
$query->bindings['select'] = [];
|
||||
}
|
||||
|
||||
// Finally, we will make the proper column alias to the query and run this sub-select on
|
||||
// the query builder. Then, we will return the builder instance back to the developer
|
||||
// for further constraint chaining that needs to take place on the query as needed.
|
||||
$alias ??= Str::snake(
|
||||
preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getQuery()->getGrammar()->getValue($column)}")
|
||||
);
|
||||
|
||||
if ($function === 'exists') {
|
||||
$this->selectRaw(
|
||||
sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)),
|
||||
$query->getBindings()
|
||||
)->withCasts([$alias => 'bool']);
|
||||
} else {
|
||||
$this->selectSub(
|
||||
$function ? $query : $query->limit(1),
|
||||
$alias
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relation hashed column name for the given column and relation.
|
||||
*
|
||||
* @param string $column
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $relation
|
||||
* @return string
|
||||
*/
|
||||
protected function getRelationHashedColumn($column, $relation)
|
||||
{
|
||||
if (str_contains($column, '.')) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
return $this->getQuery()->from === $relation->getQuery()->getQuery()->from
|
||||
? "{$relation->getRelationCountHash(false)}.$column"
|
||||
: $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to count the relations.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function withCount($relations)
|
||||
{
|
||||
return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the max of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withMax($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'max');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the min of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withMin($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the sum of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withSum($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'sum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the average of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withAvg($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'avg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the existence of related models.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function withExists($relation)
|
||||
{
|
||||
return $this->withAggregate($relation, '*', 'exists');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "has" condition where clause to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<*> $hasQuery
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation<*, *, *> $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean)
|
||||
{
|
||||
$hasQuery->mergeConstraintsFrom($relation->getQuery());
|
||||
|
||||
return $this->canUseExistsForExistenceCheck($operator, $count)
|
||||
? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1)
|
||||
: $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the where constraints from another query to the current query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder<*> $from
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeConstraintsFrom(Builder $from)
|
||||
{
|
||||
$whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
|
||||
|
||||
$wheres = $from->getQuery()->from !== $this->getQuery()->from
|
||||
? $this->requalifyWhereTables(
|
||||
$from->getQuery()->wheres,
|
||||
$from->getQuery()->grammar->getValue($from->getQuery()->from),
|
||||
$this->getModel()->getTable()
|
||||
) : $from->getQuery()->wheres;
|
||||
|
||||
// Here we have some other query that we want to merge the where constraints from. We will
|
||||
// copy over any where constraints on the query as well as remove any global scopes the
|
||||
// query might have removed. Then we will return ourselves with the finished merging.
|
||||
return $this->withoutGlobalScopes(
|
||||
$from->removedScopes()
|
||||
)->mergeWheres(
|
||||
$wheres, $whereBindings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the table name for any columns with a new qualified name.
|
||||
*
|
||||
* @param array $wheres
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @return array
|
||||
*/
|
||||
protected function requalifyWhereTables(array $wheres, string $from, string $to): array
|
||||
{
|
||||
return (new BaseCollection($wheres))->map(function ($where) use ($from, $to) {
|
||||
return (new BaseCollection($where))->map(function ($value) use ($from, $to) {
|
||||
return is_string($value) && str_starts_with($value, $from.'.')
|
||||
? $to.'.'.Str::afterLast($value, '.')
|
||||
: $value;
|
||||
});
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sub-query count clause to this query.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
|
||||
{
|
||||
$this->query->addBinding($query->getBindings(), 'where');
|
||||
|
||||
return $this->where(
|
||||
new Expression('('.$query->toSql().')'),
|
||||
$operator,
|
||||
is_numeric($count) ? new Expression($count) : $count,
|
||||
$boolean
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "has relation" base query instance.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation<*, *, *>
|
||||
*/
|
||||
protected function getRelationWithoutConstraints($relation)
|
||||
{
|
||||
return Relation::noConstraints(function () use ($relation) {
|
||||
return $this->getModel()->{$relation}();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can run an "exists" query to optimize performance.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return bool
|
||||
*/
|
||||
protected function canUseExistsForExistenceCheck($operator, $count)
|
||||
{
|
||||
return ($operator === '>=' || $operator === '<') && $count === 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user