This commit is contained in:
2024-07-03 14:41:15 +03:00
commit ec69208f05
1892 changed files with 181728 additions and 0 deletions

1495
vendor/illuminate/database/Eloquent/Builder.php vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,674 @@
<?php
namespace Illuminate\Database\Eloquent;
use Illuminate\Contracts\Queue\QueueableCollection;
use Illuminate\Contracts\Queue\QueueableEntity;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Str;
use LogicException;
class Collection extends BaseCollection implements QueueableCollection
{
/**
* Find a model in the collection by key.
*
* @param mixed $key
* @param mixed $default
* @return \Illuminate\Database\Eloquent\Model|static|null
*/
public function find($key, $default = null)
{
if ($key instanceof Model) {
$key = $key->getKey();
}
if ($key instanceof Arrayable) {
$key = $key->toArray();
}
if (is_array($key)) {
if ($this->isEmpty()) {
return new static;
}
return $this->whereIn($this->first()->getKeyName(), $key);
}
return Arr::first($this->items, function ($model) use ($key) {
return $model->getKey() == $key;
}, $default);
}
/**
* Load a set of relationships onto the collection.
*
* @param array|string $relations
* @return $this
*/
public function load($relations)
{
if ($this->isNotEmpty()) {
if (is_string($relations)) {
$relations = func_get_args();
}
$query = $this->first()->newQueryWithoutRelationships()->with($relations);
$this->items = $query->eagerLoadRelations($this->items);
}
return $this;
}
/**
* Load a set of relationship counts onto the collection.
*
* @param array|string $relations
* @return $this
*/
public function loadCount($relations)
{
if ($this->isEmpty()) {
return $this;
}
$models = $this->first()->newModelQuery()
->whereKey($this->modelKeys())
->select($this->first()->getKeyName())
->withCount(...func_get_args())
->get()
->keyBy($this->first()->getKeyName());
$attributes = Arr::except(
array_keys($models->first()->getAttributes()),
$models->first()->getKeyName()
);
$this->each(function ($model) use ($models, $attributes) {
$extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes);
$model->forceFill($extraAttributes)->syncOriginalAttributes($attributes);
});
return $this;
}
/**
* Load a set of relationships onto the collection if they are not already eager loaded.
*
* @param array|string $relations
* @return $this
*/
public function loadMissing($relations)
{
if (is_string($relations)) {
$relations = func_get_args();
}
foreach ($relations as $key => $value) {
if (is_numeric($key)) {
$key = $value;
}
$segments = explode('.', explode(':', $key)[0]);
if (Str::contains($key, ':')) {
$segments[count($segments) - 1] .= ':'.explode(':', $key)[1];
}
$path = [];
foreach ($segments as $segment) {
$path[] = [$segment => $segment];
}
if (is_callable($value)) {
$path[count($segments) - 1][end($segments)] = $value;
}
$this->loadMissingRelation($this, $path);
}
return $this;
}
/**
* Load a relationship path if it is not already eager loaded.
*
* @param \Illuminate\Database\Eloquent\Collection $models
* @param array $path
* @return void
*/
protected function loadMissingRelation(self $models, array $path)
{
$relation = array_shift($path);
$name = explode(':', key($relation))[0];
if (is_string(reset($relation))) {
$relation = reset($relation);
}
$models->filter(function ($model) use ($name) {
return ! is_null($model) && ! $model->relationLoaded($name);
})->load($relation);
if (empty($path)) {
return;
}
$models = $models->pluck($name);
if ($models->first() instanceof BaseCollection) {
$models = $models->collapse();
}
$this->loadMissingRelation(new static($models), $path);
}
/**
* Load a set of relationships onto the mixed relationship collection.
*
* @param string $relation
* @param array $relations
* @return $this
*/
public function loadMorph($relation, $relations)
{
$this->pluck($relation)
->filter()
->groupBy(function ($model) {
return get_class($model);
})
->each(function ($models, $className) use ($relations) {
static::make($models)->load($relations[$className] ?? []);
});
return $this;
}
/**
* Load a set of relationship counts onto the mixed relationship collection.
*
* @param string $relation
* @param array $relations
* @return $this
*/
public function loadMorphCount($relation, $relations)
{
$this->pluck($relation)
->filter()
->groupBy(function ($model) {
return get_class($model);
})
->each(function ($models, $className) use ($relations) {
static::make($models)->loadCount($relations[$className] ?? []);
});
return $this;
}
/**
* Determine if a key exists in the collection.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function contains($key, $operator = null, $value = null)
{
if (func_num_args() > 1 || $this->useAsCallable($key)) {
return parent::contains(...func_get_args());
}
if ($key instanceof Model) {
return parent::contains(function ($model) use ($key) {
return $model->is($key);
});
}
return parent::contains(function ($model) use ($key) {
return $model->getKey() == $key;
});
}
/**
* Get the array of primary keys.
*
* @return array
*/
public function modelKeys()
{
return array_map(function ($model) {
return $model->getKey();
}, $this->items);
}
/**
* Merge the collection with the given items.
*
* @param \ArrayAccess|array $items
* @return static
*/
public function merge($items)
{
$dictionary = $this->getDictionary();
foreach ($items as $item) {
$dictionary[$item->getKey()] = $item;
}
return new static(array_values($dictionary));
}
/**
* Run a map over each of the items.
*
* @param callable $callback
* @return \Illuminate\Support\Collection|static
*/
public function map(callable $callback)
{
$result = parent::map($callback);
return $result->contains(function ($item) {
return ! $item instanceof Model;
}) ? $result->toBase() : $result;
}
/**
* Run an associative map over each of the items.
*
* The callback should return an associative array with a single key / value pair.
*
* @param callable $callback
* @return \Illuminate\Support\Collection|static
*/
public function mapWithKeys(callable $callback)
{
$result = parent::mapWithKeys($callback);
return $result->contains(function ($item) {
return ! $item instanceof Model;
}) ? $result->toBase() : $result;
}
/**
* Reload a fresh model instance from the database for all the entities.
*
* @param array|string $with
* @return static
*/
public function fresh($with = [])
{
if ($this->isEmpty()) {
return new static;
}
$model = $this->first();
$freshModels = $model->newQueryWithoutScopes()
->with(is_string($with) ? func_get_args() : $with)
->whereIn($model->getKeyName(), $this->modelKeys())
->get()
->getDictionary();
return $this->map(function ($model) use ($freshModels) {
return $model->exists && isset($freshModels[$model->getKey()])
? $freshModels[$model->getKey()] : null;
});
}
/**
* Diff the collection with the given items.
*
* @param \ArrayAccess|array $items
* @return static
*/
public function diff($items)
{
$diff = new static;
$dictionary = $this->getDictionary($items);
foreach ($this->items as $item) {
if (! isset($dictionary[$item->getKey()])) {
$diff->add($item);
}
}
return $diff;
}
/**
* Intersect the collection with the given items.
*
* @param \ArrayAccess|array $items
* @return static
*/
public function intersect($items)
{
$intersect = new static;
if (empty($items)) {
return $intersect;
}
$dictionary = $this->getDictionary($items);
foreach ($this->items as $item) {
if (isset($dictionary[$item->getKey()])) {
$intersect->add($item);
}
}
return $intersect;
}
/**
* Return only unique items from the collection.
*
* @param string|callable|null $key
* @param bool $strict
* @return static
*/
public function unique($key = null, $strict = false)
{
if (! is_null($key)) {
return parent::unique($key, $strict);
}
return new static(array_values($this->getDictionary()));
}
/**
* Returns only the models from the collection with the specified keys.
*
* @param mixed $keys
* @return static
*/
public function only($keys)
{
if (is_null($keys)) {
return new static($this->items);
}
$dictionary = Arr::only($this->getDictionary(), $keys);
return new static(array_values($dictionary));
}
/**
* Returns all models in the collection except the models with specified keys.
*
* @param mixed $keys
* @return static
*/
public function except($keys)
{
$dictionary = Arr::except($this->getDictionary(), $keys);
return new static(array_values($dictionary));
}
/**
* Make the given, typically visible, attributes hidden across the entire collection.
*
* @param array|string $attributes
* @return $this
*/
public function makeHidden($attributes)
{
return $this->each->makeHidden($attributes);
}
/**
* Make the given, typically hidden, attributes visible across the entire collection.
*
* @param array|string $attributes
* @return $this
*/
public function makeVisible($attributes)
{
return $this->each->makeVisible($attributes);
}
/**
* Append an attribute across the entire collection.
*
* @param array|string $attributes
* @return $this
*/
public function append($attributes)
{
return $this->each->append($attributes);
}
/**
* Get a dictionary keyed by primary keys.
*
* @param \ArrayAccess|array|null $items
* @return array
*/
public function getDictionary($items = null)
{
$items = is_null($items) ? $this->items : $items;
$dictionary = [];
foreach ($items as $value) {
$dictionary[$value->getKey()] = $value;
}
return $dictionary;
}
/**
* The following methods are intercepted to always return base collections.
*/
/**
* Get an array with the values of a given key.
*
* @param string|array $value
* @param string|null $key
* @return \Illuminate\Support\Collection
*/
public function pluck($value, $key = null)
{
return $this->toBase()->pluck($value, $key);
}
/**
* Get the keys of the collection items.
*
* @return \Illuminate\Support\Collection
*/
public function keys()
{
return $this->toBase()->keys();
}
/**
* Zip the collection together with one or more arrays.
*
* @param mixed ...$items
* @return \Illuminate\Support\Collection
*/
public function zip($items)
{
return $this->toBase()->zip(...func_get_args());
}
/**
* Collapse the collection of items into a single array.
*
* @return \Illuminate\Support\Collection
*/
public function collapse()
{
return $this->toBase()->collapse();
}
/**
* Get a flattened array of the items in the collection.
*
* @param int $depth
* @return \Illuminate\Support\Collection
*/
public function flatten($depth = INF)
{
return $this->toBase()->flatten($depth);
}
/**
* Flip the items in the collection.
*
* @return \Illuminate\Support\Collection
*/
public function flip()
{
return $this->toBase()->flip();
}
/**
* Pad collection to the specified length with a value.
*
* @param int $size
* @param mixed $value
* @return \Illuminate\Support\Collection
*/
public function pad($size, $value)
{
return $this->toBase()->pad($size, $value);
}
/**
* Get the comparison function to detect duplicates.
*
* @param bool $strict
* @return \Closure
*/
protected function duplicateComparator($strict)
{
return function ($a, $b) {
return $a->is($b);
};
}
/**
* Get the type of the entities being queued.
*
* @return string|null
*
* @throws \LogicException
*/
public function getQueueableClass()
{
if ($this->isEmpty()) {
return;
}
$class = get_class($this->first());
$this->each(function ($model) use ($class) {
if (get_class($model) !== $class) {
throw new LogicException('Queueing collections with multiple model types is not supported.');
}
});
return $class;
}
/**
* Get the identifiers for all of the entities.
*
* @return array
*/
public function getQueueableIds()
{
if ($this->isEmpty()) {
return [];
}
return $this->first() instanceof QueueableEntity
? $this->map->getQueueableId()->all()
: $this->modelKeys();
}
/**
* Get the relationships of the entities being queued.
*
* @return array
*/
public function getQueueableRelations()
{
if ($this->isEmpty()) {
return [];
}
$relations = $this->map->getQueueableRelations()->all();
if (count($relations) === 0 || $relations === [[]]) {
return [];
} elseif (count($relations) === 1) {
return reset($relations);
} else {
return array_intersect(...$relations);
}
}
/**
* Get the connection of the entities being queued.
*
* @return string|null
*
* @throws \LogicException
*/
public function getQueueableConnection()
{
if ($this->isEmpty()) {
return;
}
$connection = $this->first()->getConnectionName();
$this->each(function ($model) use ($connection) {
if ($model->getConnectionName() !== $connection) {
throw new LogicException('Queueing collections with multiple model connections is not supported.');
}
});
return $connection;
}
/**
* Get the Eloquent query builder from the collection.
*
* @return \Illuminate\Database\Eloquent\Builder
*
* @throws \LogicException
*/
public function toQuery()
{
$model = $this->first();
if (! $model) {
throw new LogicException('Unable to create query for empty collection.');
}
$class = get_class($model);
if ($this->filter(function ($model) use ($class) {
return ! $model instanceof $class;
})->isNotEmpty()) {
throw new LogicException('Unable to create query for collection with mixed types.');
}
return $model->newModelQuery()->whereKey($this->modelKeys());
}
}

View File

@ -0,0 +1,250 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Support\Str;
trait GuardsAttributes
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [];
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
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
*/
protected static $guardableColumns = [];
/**
* Get the fillable attributes for the model.
*
* @return array
*/
public function getFillable()
{
return $this->fillable;
}
/**
* Set the fillable attributes for the model.
*
* @param array $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 $fillable
* @return $this
*/
public function mergeFillable(array $fillable)
{
$this->fillable = array_merge($this->fillable, $fillable);
return $this;
}
/**
* Get the guarded attributes for the model.
*
* @return array
*/
public function getGuarded()
{
return $this->guarded;
}
/**
* Set the guarded attributes for the model.
*
* @param array $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 $guarded
* @return $this
*/
public function mergeGuarded(array $guarded)
{
$this->guarded = 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 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()) &&
strpos($key, '.') === false &&
! Str::startsWith($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 (! isset(static::$guardableColumns[get_class($this)])) {
static::$guardableColumns[get_class($this)] = $this->getConnection()
->getSchemaBuilder()
->getColumnListing($this->getTable());
}
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

View File

@ -0,0 +1,415 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Events\NullDispatcher;
use Illuminate\Support\Arr;
use InvalidArgumentException;
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 = [];
/**
* 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', '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 \Closure|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 \Closure|string $callback
* @return void
*/
public static function retrieved($callback)
{
static::registerModelEvent('retrieved', $callback);
}
/**
* Register a saving model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function saving($callback)
{
static::registerModelEvent('saving', $callback);
}
/**
* Register a saved model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function saved($callback)
{
static::registerModelEvent('saved', $callback);
}
/**
* Register an updating model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function updating($callback)
{
static::registerModelEvent('updating', $callback);
}
/**
* Register an updated model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function updated($callback)
{
static::registerModelEvent('updated', $callback);
}
/**
* Register a creating model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function creating($callback)
{
static::registerModelEvent('creating', $callback);
}
/**
* Register a created model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function created($callback)
{
static::registerModelEvent('created', $callback);
}
/**
* Register a replicating model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function replicating($callback)
{
static::registerModelEvent('replicating', $callback);
}
/**
* Register a deleting model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function deleting($callback)
{
static::registerModelEvent('deleting', $callback);
}
/**
* Register a deleted model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function deleted($callback)
{
static::registerModelEvent('deleted', $callback);
}
/**
* Remove all of 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 (array_values($instance->dispatchesEvents) as $event) {
static::$dispatcher->forget($event);
}
}
/**
* Get the event dispatcher instance.
*
* @return \Illuminate\Contracts\Events\Dispatcher
*/
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);
}
}
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Closure;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Arr;
use InvalidArgumentException;
trait HasGlobalScopes
{
/**
* Register a new global scope on the model.
*
* @param \Illuminate\Database\Eloquent\Scope|\Closure|string $scope
* @param \Closure|null $implementation
* @return mixed
*
* @throws \InvalidArgumentException
*/
public static function addGlobalScope($scope, Closure $implementation = null)
{
if (is_string($scope) && ! is_null($implementation)) {
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;
}
throw new InvalidArgumentException('Global scope must be an instance of Closure or 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 the global scopes for this class instance.
*
* @return array
*/
public function getGlobalScopes()
{
return Arr::get(static::$globalScopes, static::class, []);
}
}

View File

@ -0,0 +1,870 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
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\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 array
*/
public static $manyMethods = [
'belongsToMany', 'morphToMany', 'morphedByMany',
];
/**
* The relation resolver callbacks.
*
* @var array
*/
protected static $relationResolvers = [];
/**
* 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.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
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->getTable().'.'.$foreignKey, $localKey);
}
/**
* Instantiate a new HasOne relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
{
return new HasOne($query, $parent, $foreignKey, $localKey);
}
/**
* Define a has-one-through relationship.
*
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|null $secondKey
* @param string|null $localKey
* @param string|null $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
public function hasOneThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
{
$through = new $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.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $farParent
* @param \Illuminate\Database\Eloquent\Model $throughParent
* @param string $firstKey
* @param string $secondKey
* @param string $localKey
* @param string $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
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.
*
* @param string $related
* @param string $name
* @param string|null $type
* @param string|null $id
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
[$type, $id] = $this->getMorphs($name, $type, $id);
$table = $instance->getTable();
$localKey = $localKey ?: $this->getKeyName();
return $this->newMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}
/**
* Instantiate a new MorphOne relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
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.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $ownerKey
* @param string|null $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
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 returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$ownerKey = $ownerKey ?: $instance->getKeyName();
return $this->newBelongsTo(
$instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
);
}
/**
* Instantiate a new BelongsTo relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $child
* @param string $foreignKey
* @param string $ownerKey
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
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
*/
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->{$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 $ownerKey
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
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 $ownerKey
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
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.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $ownerKey
* @param string $type
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
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'];
}
/**
* Define a one-to-many relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
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->getTable().'.'.$foreignKey, $localKey
);
}
/**
* Instantiate a new HasMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
{
return new HasMany($query, $parent, $foreignKey, $localKey);
}
/**
* Define a has-many-through relationship.
*
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|null $secondKey
* @param string|null $localKey
* @param string|null $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
{
$through = new $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.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $farParent
* @param \Illuminate\Database\Eloquent\Model $throughParent
* @param string $firstKey
* @param string $secondKey
* @param string $localKey
* @param string $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
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.
*
* @param string $related
* @param string $name
* @param string|null $type
* @param string|null $id
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
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);
$table = $instance->getTable();
$localKey = $localKey ?: $this->getKeyName();
return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}
/**
* Instantiate a new MorphMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
{
return new MorphMany($query, $parent, $type, $id, $localKey);
}
/**
* Define a many-to-many relationship.
*
* @param string $related
* @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\BelongsToMany
*/
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.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
* @param string $relatedKey
* @param string|null $relationName
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
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.
*
* @param string $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 bool $inverse
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphToMany($related, $name, $table = null, $foreignPivotKey = null,
$relatedPivotKey = null, $parentKey = null,
$relatedKey = null, $inverse = false)
{
$caller = $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 this related model and
// the relationship instances for this relation. This relations will set
// appropriate query constraints then entirely manages 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(), $caller, $inverse
);
}
/**
* Instantiate a new MorphToMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $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
*/
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.
*
* @param string $related
* @param string $name
* @param string|null $table
* @param string|null $foreignPivotKey
* @param string|null $relatedPivotKey
* @param string|null $parentKey
* @param string|null $relatedKey
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
$relatedPivotKey = null, $parentKey = null, $relatedKey = 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, 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()
{
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 Collection) {
$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);
}
return static::class;
}
/**
* Create a new model instance for a related model.
*
* @param string $class
* @return mixed
*/
protected function newRelatedInstance($class)
{
return tap(new $class, function ($instance) {
if (! $instance->getConnectionName()) {
$instance->setConnection($this->connection);
}
});
}
/**
* 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;
}
}

View File

@ -0,0 +1,149 @@
<?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;
/**
* Update the model's update timestamp.
*
* @return bool
*/
public function touch()
{
if (! $this->usesTimestamps()) {
return false;
}
$this->updateTimestamps();
return $this->save();
}
/**
* Update the creation and update timestamps.
*
* @return void
*/
protected 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);
}
}
/**
* 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;
}
/**
* 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
*/
public function getQualifiedCreatedAtColumn()
{
return $this->qualifyColumn($this->getCreatedAtColumn());
}
/**
* Get the fully qualified "updated at" column.
*
* @return string
*/
public function getQualifiedUpdatedAtColumn()
{
return $this->qualifyColumn($this->getUpdatedAtColumn());
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Closure;
trait HidesAttributes
{
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [];
/**
* The attributes that should be visible in serialization.
*
* @var array
*/
protected $visible = [];
/**
* Get the hidden attributes for the model.
*
* @return array
*/
public function getHidden()
{
return $this->hidden;
}
/**
* Set the hidden attributes for the model.
*
* @param array $hidden
* @return $this
*/
public function setHidden(array $hidden)
{
$this->hidden = $hidden;
return $this;
}
/**
* Get the visible attributes for the model.
*
* @return array
*/
public function getVisible()
{
return $this->visible;
}
/**
* Set the visible attributes for the model.
*
* @param array $visible
* @return $this
*/
public function setVisible(array $visible)
{
$this->visible = $visible;
return $this;
}
/**
* Make the given, typically hidden, attributes visible.
*
* @param array|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_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|null $attributes
* @return $this
*/
public function makeVisibleIf($condition, $attributes)
{
$condition = $condition instanceof Closure ? $condition($this) : $condition;
return $condition ? $this->makeVisible($attributes) : $this;
}
/**
* Make the given, typically visible, attributes hidden.
*
* @param array|string|null $attributes
* @return $this
*/
public function makeHidden($attributes)
{
$this->hidden = 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|null $attributes
* @return $this
*/
public function makeHiddenIf($condition, $attributes)
{
$condition = $condition instanceof Closure ? $condition($this) : $condition;
return value($condition) ? $this->makeHidden($attributes) : $this;
}
}

View File

@ -0,0 +1,498 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Closure;
use Illuminate\Database\Eloquent\Builder;
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\Str;
use RuntimeException;
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 \Illuminate\Database\Eloquent\Builder|static
*
* @throws \RuntimeException
*/
public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
if (is_string($relation)) {
if (strpos($relation, '.') !== false) {
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
}
$relation = $this->getRelationWithoutConstraints($relation);
}
if ($relation instanceof MorphTo) {
throw new RuntimeException('Please use whereHasMorph() for MorphTo relationships.');
}
// 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 \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orHas($relation, $operator = '>=', $count = 1)
{
return $this->has($relation, $operator, $count, 'or');
}
/**
* Add a relationship count / exists condition to the query.
*
* @param string $relation
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orDoesntHave($relation)
{
return $this->doesntHave($relation, 'or');
}
/**
* Add a relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 and an "or".
*
* @param string $relation
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereDoesntHave($relation, Closure $callback = null)
{
return $this->doesntHave($relation, 'or', $callback);
}
/**
* Add a polymorphic relationship count / exists condition to the query.
*
* @param string $relation
* @param string|array $types
* @param string $operator
* @param int $count
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
$relation = $this->getRelationWithoutConstraints($relation);
$types = (array) $types;
if ($types === ['*']) {
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();
}
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->query->from.'.'.$relation->getMorphType(), '=', (new $type)->getMorphClass())
->whereHas($belongsTo, $callback, $operator, $count);
});
}
}, null, null, $boolean);
}
/**
* Get the BelongsTo relationship for a single polymorphic type.
*
* @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation
* @param string $type
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
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 string $relation
* @param string|array $types
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string|array $types
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string|array $types
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string|array $types
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string|array $types
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string|array $types
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
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 string $relation
* @param string|array $types
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null)
{
return $this->doesntHaveMorph($relation, $types, 'or', $callback);
}
/**
* Add subselect queries to count the relations.
*
* @param mixed $relations
* @return $this
*/
public function withCount($relations)
{
if (empty($relations)) {
return $this;
}
if (is_null($this->query->columns)) {
$this->query->select([$this->query->from.'.*']);
}
$relations = is_array($relations) ? $relations : func_get_args();
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 counts on the same relationship name.
$segments = explode(' ', $name);
unset($alias);
if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
[$name, $alias] = [$segments[0], $segments[2]];
}
$relation = $this->getRelationWithoutConstraints($name);
// Here we will get the relationship count 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
// count query. We will normalize the relation name then append _count as the name.
$query = $relation->getRelationExistenceCountQuery(
$relation->getRelated()->newQuery(), $this
);
$query->callScope($constraints);
$query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
$query->orders = null;
$query->setBindings([], 'order');
if (count($query->columns) > 1) {
$query->columns = [$query->columns[0]];
$query->bindings['select'] = [];
}
// Finally we will add the proper result column alias to the query and run the subselect
// statement against the query builder. Then we will return the builder instance back
// to the developer for further constraint chaining that needs to take place on it.
$column = $alias ?? Str::snake($name.'_count');
$this->selectSub($query, $column);
}
return $this;
}
/**
* 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 \Illuminate\Database\Eloquent\Builder|static
*/
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 \Illuminate\Database\Eloquent\Builder|static
*/
public function mergeConstraintsFrom(Builder $from)
{
$whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
// 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(
$from->getQuery()->wheres, $whereBindings
);
}
/**
* 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;
}
}

View File

@ -0,0 +1,271 @@
<?php
namespace Illuminate\Database\Eloquent;
use ArrayAccess;
use Faker\Generator as Faker;
use Symfony\Component\Finder\Finder;
class Factory implements ArrayAccess
{
/**
* The model definitions in the container.
*
* @var array
*/
protected $definitions = [];
/**
* The registered model states.
*
* @var array
*/
protected $states = [];
/**
* The registered after making callbacks.
*
* @var array
*/
protected $afterMaking = [];
/**
* The registered after creating callbacks.
*
* @var array
*/
protected $afterCreating = [];
/**
* The Faker instance for the builder.
*
* @var \Faker\Generator
*/
protected $faker;
/**
* Create a new factory instance.
*
* @param \Faker\Generator $faker
* @return void
*/
public function __construct(Faker $faker)
{
$this->faker = $faker;
}
/**
* Create a new factory container.
*
* @param \Faker\Generator $faker
* @param string|null $pathToFactories
* @return static
*/
public static function construct(Faker $faker, $pathToFactories = null)
{
$pathToFactories = $pathToFactories ?: database_path('factories');
return (new static($faker))->load($pathToFactories);
}
/**
* Define a class with a given set of attributes.
*
* @param string $class
* @param callable $attributes
* @return $this
*/
public function define($class, callable $attributes)
{
$this->definitions[$class] = $attributes;
return $this;
}
/**
* Define a state with a given set of attributes.
*
* @param string $class
* @param string $state
* @param callable|array $attributes
* @return $this
*/
public function state($class, $state, $attributes)
{
$this->states[$class][$state] = $attributes;
return $this;
}
/**
* Define a callback to run after making a model.
*
* @param string $class
* @param callable $callback
* @param string $name
* @return $this
*/
public function afterMaking($class, callable $callback, $name = 'default')
{
$this->afterMaking[$class][$name][] = $callback;
return $this;
}
/**
* Define a callback to run after making a model with given state.
*
* @param string $class
* @param string $state
* @param callable $callback
* @return $this
*/
public function afterMakingState($class, $state, callable $callback)
{
return $this->afterMaking($class, $callback, $state);
}
/**
* Define a callback to run after creating a model.
*
* @param string $class
* @param callable $callback
* @param string $name
* @return $this
*/
public function afterCreating($class, callable $callback, $name = 'default')
{
$this->afterCreating[$class][$name][] = $callback;
return $this;
}
/**
* Define a callback to run after creating a model with given state.
*
* @param string $class
* @param string $state
* @param callable $callback
* @return $this
*/
public function afterCreatingState($class, $state, callable $callback)
{
return $this->afterCreating($class, $callback, $state);
}
/**
* Create an instance of the given model and persist it to the database.
*
* @param string $class
* @param array $attributes
* @return mixed
*/
public function create($class, array $attributes = [])
{
return $this->of($class)->create($attributes);
}
/**
* Create an instance of the given model.
*
* @param string $class
* @param array $attributes
* @return mixed
*/
public function make($class, array $attributes = [])
{
return $this->of($class)->make($attributes);
}
/**
* Get the raw attribute array for a given model.
*
* @param string $class
* @param array $attributes
* @return array
*/
public function raw($class, array $attributes = [])
{
return array_merge(
call_user_func($this->definitions[$class], $this->faker), $attributes
);
}
/**
* Create a builder for the given model.
*
* @param string $class
* @return \Illuminate\Database\Eloquent\FactoryBuilder
*/
public function of($class)
{
return new FactoryBuilder(
$class, $this->definitions, $this->states,
$this->afterMaking, $this->afterCreating, $this->faker
);
}
/**
* Load factories from path.
*
* @param string $path
* @return $this
*/
public function load($path)
{
$factory = $this;
if (is_dir($path)) {
foreach (Finder::create()->files()->name('*.php')->in($path) as $file) {
require $file->getRealPath();
}
}
return $factory;
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->definitions[$offset]);
}
/**
* Get the value of the given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->make($offset);
}
/**
* Set the given offset to the given value.
*
* @param string $offset
* @param callable $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->define($offset, $value);
}
/**
* Unset the value at the given offset.
*
* @param string $offset
* @return void
*/
public function offsetUnset($offset)
{
unset($this->definitions[$offset]);
}
}

View File

@ -0,0 +1,449 @@
<?php
namespace Illuminate\Database\Eloquent;
use Faker\Generator as Faker;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
class FactoryBuilder
{
use Macroable;
/**
* The model definitions in the container.
*
* @var array
*/
protected $definitions;
/**
* The model being built.
*
* @var string
*/
protected $class;
/**
* The database connection on which the model instance should be persisted.
*
* @var string
*/
protected $connection;
/**
* The model states.
*
* @var array
*/
protected $states;
/**
* The model after making callbacks.
*
* @var array
*/
protected $afterMaking = [];
/**
* The model after creating callbacks.
*
* @var array
*/
protected $afterCreating = [];
/**
* The states to apply.
*
* @var array
*/
protected $activeStates = [];
/**
* The Faker instance for the builder.
*
* @var \Faker\Generator
*/
protected $faker;
/**
* The number of models to build.
*
* @var int|null
*/
protected $amount = null;
/**
* Create an new builder instance.
*
* @param string $class
* @param array $definitions
* @param array $states
* @param array $afterMaking
* @param array $afterCreating
* @param \Faker\Generator $faker
* @return void
*/
public function __construct($class, array $definitions, array $states,
array $afterMaking, array $afterCreating, Faker $faker)
{
$this->class = $class;
$this->faker = $faker;
$this->states = $states;
$this->definitions = $definitions;
$this->afterMaking = $afterMaking;
$this->afterCreating = $afterCreating;
}
/**
* Set the amount of models you wish to create / make.
*
* @param int $amount
* @return $this
*/
public function times($amount)
{
$this->amount = $amount;
return $this;
}
/**
* Set the state to be applied to the model.
*
* @param string $state
* @return $this
*/
public function state($state)
{
return $this->states([$state]);
}
/**
* Set the states to be applied to the model.
*
* @param array|mixed $states
* @return $this
*/
public function states($states)
{
$this->activeStates = is_array($states) ? $states : func_get_args();
return $this;
}
/**
* Set the database connection on which the model instance should be persisted.
*
* @param string $name
* @return $this
*/
public function connection($name)
{
$this->connection = $name;
return $this;
}
/**
* Create a model and persist it in the database if requested.
*
* @param array $attributes
* @return \Closure
*/
public function lazy(array $attributes = [])
{
return function () use ($attributes) {
return $this->create($attributes);
};
}
/**
* Create a collection of models and persist them to the database.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|mixed
*/
public function create(array $attributes = [])
{
$results = $this->make($attributes);
if ($results instanceof Model) {
$this->store(collect([$results]));
$this->callAfterCreating(collect([$results]));
} else {
$this->store($results);
$this->callAfterCreating($results);
}
return $results;
}
/**
* Create a collection of models and persist them to the database.
*
* @param iterable $records
* @return \Illuminate\Database\Eloquent\Collection|mixed
*/
public function createMany(iterable $records)
{
return (new $this->class)->newCollection(array_map(function ($attribute) {
return $this->create($attribute);
}, $records));
}
/**
* Set the connection name on the results and store them.
*
* @param \Illuminate\Support\Collection $results
* @return void
*/
protected function store($results)
{
$results->each(function ($model) {
if (! isset($this->connection)) {
$model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName());
}
$model->save();
});
}
/**
* Create a collection of models.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|mixed
*/
public function make(array $attributes = [])
{
if ($this->amount === null) {
return tap($this->makeInstance($attributes), function ($instance) {
$this->callAfterMaking(collect([$instance]));
});
}
if ($this->amount < 1) {
return (new $this->class)->newCollection();
}
$instances = (new $this->class)->newCollection(array_map(function () use ($attributes) {
return $this->makeInstance($attributes);
}, range(1, $this->amount)));
$this->callAfterMaking($instances);
return $instances;
}
/**
* Create an array of raw attribute arrays.
*
* @param array $attributes
* @return mixed
*/
public function raw(array $attributes = [])
{
if ($this->amount === null) {
return $this->getRawAttributes($attributes);
}
if ($this->amount < 1) {
return [];
}
return array_map(function () use ($attributes) {
return $this->getRawAttributes($attributes);
}, range(1, $this->amount));
}
/**
* Get a raw attributes array for the model.
*
* @param array $attributes
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected function getRawAttributes(array $attributes = [])
{
if (! isset($this->definitions[$this->class])) {
throw new InvalidArgumentException("Unable to locate factory for [{$this->class}].");
}
$definition = call_user_func(
$this->definitions[$this->class],
$this->faker, $attributes
);
return $this->expandAttributes(
array_merge($this->applyStates($definition, $attributes), $attributes)
);
}
/**
* Make an instance of the model with the given attributes.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
protected function makeInstance(array $attributes = [])
{
return Model::unguarded(function () use ($attributes) {
$instance = new $this->class(
$this->getRawAttributes($attributes)
);
if (isset($this->connection)) {
$instance->setConnection($this->connection);
}
return $instance;
});
}
/**
* Apply the active states to the model definition array.
*
* @param array $definition
* @param array $attributes
* @return array
*
* @throws \InvalidArgumentException
*/
protected function applyStates(array $definition, array $attributes = [])
{
foreach ($this->activeStates as $state) {
if (! isset($this->states[$this->class][$state])) {
if ($this->stateHasAfterCallback($state)) {
continue;
}
throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}].");
}
$definition = array_merge(
$definition,
$this->stateAttributes($state, $attributes)
);
}
return $definition;
}
/**
* Get the state attributes.
*
* @param string $state
* @param array $attributes
* @return array
*/
protected function stateAttributes($state, array $attributes)
{
$stateAttributes = $this->states[$this->class][$state];
if (! is_callable($stateAttributes)) {
return $stateAttributes;
}
return $stateAttributes($this->faker, $attributes);
}
/**
* Expand all attributes to their underlying values.
*
* @param array $attributes
* @return array
*/
protected function expandAttributes(array $attributes)
{
foreach ($attributes as &$attribute) {
if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
$attribute = $attribute($attributes);
}
if ($attribute instanceof static) {
$attribute = $attribute->create()->getKey();
}
if ($attribute instanceof Model) {
$attribute = $attribute->getKey();
}
}
return $attributes;
}
/**
* Run after making callbacks on a collection of models.
*
* @param \Illuminate\Support\Collection $models
* @return void
*/
public function callAfterMaking($models)
{
$this->callAfter($this->afterMaking, $models);
}
/**
* Run after creating callbacks on a collection of models.
*
* @param \Illuminate\Support\Collection $models
* @return void
*/
public function callAfterCreating($models)
{
$this->callAfter($this->afterCreating, $models);
}
/**
* Call after callbacks for each model and state.
*
* @param array $afterCallbacks
* @param \Illuminate\Support\Collection $models
* @return void
*/
protected function callAfter(array $afterCallbacks, $models)
{
$states = array_merge(['default'], $this->activeStates);
$models->each(function ($model) use ($states, $afterCallbacks) {
foreach ($states as $state) {
$this->callAfterCallbacks($afterCallbacks, $model, $state);
}
});
}
/**
* Call after callbacks for each model and state.
*
* @param array $afterCallbacks
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $state
* @return void
*/
protected function callAfterCallbacks(array $afterCallbacks, $model, $state)
{
if (! isset($afterCallbacks[$this->class][$state])) {
return;
}
foreach ($afterCallbacks[$this->class][$state] as $callback) {
$callback($model, $this->faker);
}
}
/**
* Determine if the given state has an "after" callback.
*
* @param string $state
* @return bool
*/
protected function stateHasAfterCallback($state)
{
return isset($this->afterMaking[$this->class][$state]) ||
isset($this->afterCreating[$this->class][$state]);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Illuminate\Database\Eloquent;
/**
* @mixin \Illuminate\Database\Eloquent\Builder
*/
class HigherOrderBuilderProxy
{
/**
* The collection being operated on.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $builder;
/**
* The method being proxied.
*
* @var string
*/
protected $method;
/**
* Create a new proxy instance.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param string $method
* @return void
*/
public function __construct(Builder $builder, $method)
{
$this->method = $method;
$this->builder = $builder;
}
/**
* Proxy a scope call onto the query builder.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->builder->{$this->method}(function ($value) use ($method, $parameters) {
return $value->{$method}(...$parameters);
});
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Illuminate\Database\Eloquent;
use RuntimeException;
class JsonEncodingException extends RuntimeException
{
/**
* Create a new JSON encoding exception for the model.
*
* @param mixed $model
* @param string $message
* @return static
*/
public static function forModel($model, $message)
{
return new static('Error encoding model ['.get_class($model).'] with ID ['.$model->getKey().'] to JSON: '.$message);
}
/**
* Create a new JSON encoding exception for the resource.
*
* @param \Illuminate\Http\Resources\Json\JsonResource $resource
* @param string $message
* @return static
*/
public static function forResource($resource, $message)
{
$model = $resource->resource;
return new static('Error encoding resource ['.get_class($resource).'] with model ['.get_class($model).'] with ID ['.$model->getKey().'] to JSON: '.$message);
}
/**
* Create a new JSON encoding exception for an attribute.
*
* @param mixed $model
* @param mixed $key
* @param string $message
* @return static
*/
public static function forAttribute($model, $key, $message)
{
$class = get_class($model);
return new static("Unable to encode attribute [{$key}] for model [{$class}] to JSON: {$message}.");
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Illuminate\Database\Eloquent;
use RuntimeException;
class MassAssignmentException extends RuntimeException
{
//
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
<?php
namespace Illuminate\Database\Eloquent;
use Illuminate\Support\Arr;
use RuntimeException;
class ModelNotFoundException extends RuntimeException
{
/**
* Name of the affected Eloquent model.
*
* @var string
*/
protected $model;
/**
* The affected model IDs.
*
* @var int|array
*/
protected $ids;
/**
* Set the affected Eloquent model and instance ids.
*
* @param string $model
* @param int|array $ids
* @return $this
*/
public function setModel($model, $ids = [])
{
$this->model = $model;
$this->ids = Arr::wrap($ids);
$this->message = "No query results for model [{$model}]";
if (count($this->ids) > 0) {
$this->message .= ' '.implode(', ', $this->ids);
} else {
$this->message .= '.';
}
return $this;
}
/**
* Get the affected Eloquent model.
*
* @return string
*/
public function getModel()
{
return $this->model;
}
/**
* Get the affected Eloquent model IDs.
*
* @return int|array
*/
public function getIds()
{
return $this->ids;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Illuminate\Database\Eloquent;
use Illuminate\Contracts\Queue\EntityNotFoundException;
use Illuminate\Contracts\Queue\EntityResolver as EntityResolverContract;
class QueueEntityResolver implements EntityResolverContract
{
/**
* Resolve the entity for the given ID.
*
* @param string $type
* @param mixed $id
* @return mixed
*
* @throws \Illuminate\Contracts\Queue\EntityNotFoundException
*/
public function resolve($type, $id)
{
$instance = (new $type)->find($id);
if ($instance) {
return $instance;
}
throw new EntityNotFoundException($type, $id);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Illuminate\Database\Eloquent;
use RuntimeException;
class RelationNotFoundException extends RuntimeException
{
/**
* The name of the affected Eloquent model.
*
* @var string
*/
public $model;
/**
* The name of the relation.
*
* @var string
*/
public $relation;
/**
* Create a new exception instance.
*
* @param object $model
* @param string $relation
* @return static
*/
public static function make($model, $relation)
{
$class = get_class($model);
$instance = new static("Call to undefined relationship [{$relation}] on model [{$class}].");
$instance->model = $class;
$instance->relation = $relation;
return $instance;
}
}

View File

@ -0,0 +1,361 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
class BelongsTo extends Relation
{
use SupportsDefaultModels;
/**
* The child model instance of the relation.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $child;
/**
* The foreign key of the parent model.
*
* @var string
*/
protected $foreignKey;
/**
* The associated key on the parent model.
*
* @var string
*/
protected $ownerKey;
/**
* The name of the relationship.
*
* @var string
*/
protected $relationName;
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new belongs to relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $child
* @param string $foreignKey
* @param string $ownerKey
* @param string $relationName
* @return void
*/
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName)
{
$this->ownerKey = $ownerKey;
$this->relationName = $relationName;
$this->foreignKey = $foreignKey;
// In the underlying base relationship class, this variable is referred to as
// the "parent" since most relationships are not inversed. But, since this
// one is we will create a "child" variable for much better readability.
$this->child = $child;
parent::__construct($query, $child);
}
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
if (is_null($this->child->{$this->foreignKey})) {
return $this->getDefaultFor($this->parent);
}
return $this->query->first() ?: $this->getDefaultFor($this->parent);
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$table = $this->related->getTable();
$this->query->where($table.'.'.$this->ownerKey, '=', $this->child->{$this->foreignKey});
}
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
// We'll grab the primary key name of the related models since it could be set to
// a non-standard name and not "id". We will then construct the constraint for
// our eagerly loading query so it returns the proper models from execution.
$key = $this->related->getTable().'.'.$this->ownerKey;
$whereIn = $this->whereInMethod($this->related, $this->ownerKey);
$this->query->{$whereIn}($key, $this->getEagerModelKeys($models));
}
/**
* Gather the keys from an array of related models.
*
* @param array $models
* @return array
*/
protected function getEagerModelKeys(array $models)
{
$keys = [];
// First we need to gather all of the keys from the parent models so we know what
// to query for via the eager loading query. We will add them to an array then
// execute a "where in" statement to gather up all of those related records.
foreach ($models as $model) {
if (! is_null($value = $model->{$this->foreignKey})) {
$keys[] = $value;
}
}
sort($keys);
return array_values(array_unique($keys));
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->getDefaultFor($model));
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
$foreign = $this->foreignKey;
$owner = $this->ownerKey;
// First we will get to build a dictionary of the child models by their primary
// key of the relationship, then we can easily match the children back onto
// the parents using that dictionary and the primary key of the children.
$dictionary = [];
foreach ($results as $result) {
$dictionary[$result->getAttribute($owner)] = $result;
}
// Once we have the dictionary constructed, we can loop through all the parents
// and match back onto their children using these keys of the dictionary and
// the primary key of the children to map them onto the correct instances.
foreach ($models as $model) {
if (isset($dictionary[$model->{$foreign}])) {
$model->setRelation($relation, $dictionary[$model->{$foreign}]);
}
}
return $models;
}
/**
* Associate the model instance to the given parent.
*
* @param \Illuminate\Database\Eloquent\Model|int|string $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function associate($model)
{
$ownerKey = $model instanceof Model ? $model->getAttribute($this->ownerKey) : $model;
$this->child->setAttribute($this->foreignKey, $ownerKey);
if ($model instanceof Model) {
$this->child->setRelation($this->relationName, $model);
} else {
$this->child->unsetRelation($this->relationName);
}
return $this->child;
}
/**
* Dissociate previously associated model from the given parent.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function dissociate()
{
$this->child->setAttribute($this->foreignKey, null);
return $this->child->setRelation($this->relationName, null);
}
/**
* Add the constraints for a relationship query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
if ($parentQuery->getQuery()->from == $query->getQuery()->from) {
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
}
return $query->select($columns)->whereColumn(
$this->getQualifiedForeignKeyName(), '=', $query->qualifyColumn($this->ownerKey)
);
}
/**
* Add the constraints for a relationship query on the same table.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$query->select($columns)->from(
$query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()
);
$query->getModel()->setTable($hash);
return $query->whereColumn(
$hash.'.'.$this->ownerKey, '=', $this->getQualifiedForeignKeyName()
);
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Determine if the related model has an auto-incrementing ID.
*
* @return bool
*/
protected function relationHasIncrementingId()
{
return $this->related->getIncrementing() &&
in_array($this->related->getKeyType(), ['int', 'integer']);
}
/**
* Make a new related instance for the given model.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model
*/
protected function newRelatedInstanceFor(Model $parent)
{
return $this->related->newInstance();
}
/**
* Get the child of the relationship.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function getChild()
{
return $this->child;
}
/**
* Get the foreign key of the relationship.
*
* @return string
*/
public function getForeignKeyName()
{
return $this->foreignKey;
}
/**
* Get the fully qualified foreign key of the relationship.
*
* @return string
*/
public function getQualifiedForeignKeyName()
{
return $this->child->qualifyColumn($this->foreignKey);
}
/**
* Get the associated key of the relationship.
*
* @return string
*/
public function getOwnerKeyName()
{
return $this->ownerKey;
}
/**
* Get the fully qualified associated key of the relationship.
*
* @return string
*/
public function getQualifiedOwnerKeyName()
{
return $this->related->qualifyColumn($this->ownerKey);
}
/**
* Get the name of the relationship.
*
* @return string
*/
public function getRelationName()
{
return $this->relationName;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,321 @@
<?php
namespace Illuminate\Database\Eloquent\Relations\Concerns;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
trait AsPivot
{
/**
* The parent model of the relationship.
*
* @var \Illuminate\Database\Eloquent\Model
*/
public $pivotParent;
/**
* The name of the foreign key column.
*
* @var string
*/
protected $foreignKey;
/**
* The name of the "other key" column.
*
* @var string
*/
protected $relatedKey;
/**
* Create a new pivot model instance.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @param array $attributes
* @param string $table
* @param bool $exists
* @return static
*/
public static function fromAttributes(Model $parent, $attributes, $table, $exists = false)
{
$instance = new static;
$instance->timestamps = $instance->hasTimestampAttributes($attributes);
// The pivot model is a "dynamic" model since we will set the tables dynamically
// for the instance. This allows it work for any intermediate tables for the
// many to many relationship that are defined by this developer's classes.
$instance->setConnection($parent->getConnectionName())
->setTable($table)
->forceFill($attributes)
->syncOriginal();
// We store off the parent instance so we will access the timestamp column names
// for the model, since the pivot model timestamps aren't easily configurable
// from the developer's point of view. We can use the parents to get these.
$instance->pivotParent = $parent;
$instance->exists = $exists;
return $instance;
}
/**
* Create a new pivot model from raw values returned from a query.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @param array $attributes
* @param string $table
* @param bool $exists
* @return static
*/
public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false)
{
$instance = static::fromAttributes($parent, [], $table, $exists);
$instance->timestamps = $instance->hasTimestampAttributes($attributes);
$instance->setRawAttributes($attributes, $exists);
return $instance;
}
/**
* Set the keys for a save update query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery(Builder $query)
{
if (isset($this->attributes[$this->getKeyName()])) {
return parent::setKeysForSaveQuery($query);
}
$query->where($this->foreignKey, $this->getOriginal(
$this->foreignKey, $this->getAttribute($this->foreignKey)
));
return $query->where($this->relatedKey, $this->getOriginal(
$this->relatedKey, $this->getAttribute($this->relatedKey)
));
}
/**
* Delete the pivot model record from the database.
*
* @return int
*/
public function delete()
{
if (isset($this->attributes[$this->getKeyName()])) {
return (int) parent::delete();
}
if ($this->fireModelEvent('deleting') === false) {
return 0;
}
$this->touchOwners();
return tap($this->getDeleteQuery()->delete(), function () {
$this->exists = false;
$this->fireModelEvent('deleted', false);
});
}
/**
* Get the query builder for a delete operation on the pivot.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function getDeleteQuery()
{
return $this->newQueryWithoutRelationships()->where([
$this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)),
$this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)),
]);
}
/**
* Get the table associated with the model.
*
* @return string
*/
public function getTable()
{
if (! isset($this->table)) {
$this->setTable(str_replace(
'\\', '', Str::snake(Str::singular(class_basename($this)))
));
}
return $this->table;
}
/**
* Get the foreign key column name.
*
* @return string
*/
public function getForeignKey()
{
return $this->foreignKey;
}
/**
* Get the "related key" column name.
*
* @return string
*/
public function getRelatedKey()
{
return $this->relatedKey;
}
/**
* Get the "related key" column name.
*
* @return string
*/
public function getOtherKey()
{
return $this->getRelatedKey();
}
/**
* Set the key names for the pivot model instance.
*
* @param string $foreignKey
* @param string $relatedKey
* @return $this
*/
public function setPivotKeys($foreignKey, $relatedKey)
{
$this->foreignKey = $foreignKey;
$this->relatedKey = $relatedKey;
return $this;
}
/**
* Determine if the pivot model or given attributes has timestamp attributes.
*
* @param array|null $attributes
* @return bool
*/
public function hasTimestampAttributes($attributes = null)
{
return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes);
}
/**
* Get the name of the "created at" column.
*
* @return string
*/
public function getCreatedAtColumn()
{
return $this->pivotParent
? $this->pivotParent->getCreatedAtColumn()
: parent::getCreatedAtColumn();
}
/**
* Get the name of the "updated at" column.
*
* @return string
*/
public function getUpdatedAtColumn()
{
return $this->pivotParent
? $this->pivotParent->getUpdatedAtColumn()
: parent::getUpdatedAtColumn();
}
/**
* Get the queueable identity for the entity.
*
* @return mixed
*/
public function getQueueableId()
{
if (isset($this->attributes[$this->getKeyName()])) {
return $this->getKey();
}
return sprintf(
'%s:%s:%s:%s',
$this->foreignKey, $this->getAttribute($this->foreignKey),
$this->relatedKey, $this->getAttribute($this->relatedKey)
);
}
/**
* Get a new query to restore one or more models by their queueable IDs.
*
* @param int[]|string[]|string $ids
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newQueryForRestoration($ids)
{
if (is_array($ids)) {
return $this->newQueryForCollectionRestoration($ids);
}
if (! Str::contains($ids, ':')) {
return parent::newQueryForRestoration($ids);
}
$segments = explode(':', $ids);
return $this->newQueryWithoutScopes()
->where($segments[0], $segments[1])
->where($segments[2], $segments[3]);
}
/**
* Get a new query to restore multiple models by their queueable IDs.
*
* @param int[]|string[] $ids
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function newQueryForCollectionRestoration(array $ids)
{
$ids = array_values($ids);
if (! Str::contains($ids[0], ':')) {
return parent::newQueryForRestoration($ids);
}
$query = $this->newQueryWithoutScopes();
foreach ($ids as $id) {
$segments = explode(':', $id);
$query->orWhere(function ($query) use ($segments) {
return $query->where($segments[0], $segments[1])
->where($segments[2], $segments[3]);
});
}
return $query;
}
/**
* Unset all the loaded relations for the instance.
*
* @return $this
*/
public function unsetRelations()
{
$this->pivotParent = null;
$this->relations = [];
return $this;
}
}

View File

@ -0,0 +1,670 @@
<?php
namespace Illuminate\Database\Eloquent\Relations\Concerns;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Support\Collection as BaseCollection;
trait InteractsWithPivotTable
{
/**
* Toggles a model (or models) from the parent.
*
* Each existing model is detached, and non existing ones are attached.
*
* @param mixed $ids
* @param bool $touch
* @return array
*/
public function toggle($ids, $touch = true)
{
$changes = [
'attached' => [], 'detached' => [],
];
$records = $this->formatRecordsList($this->parseIds($ids));
// Next, we will determine which IDs should get removed from the join table by
// checking which of the given ID/records is in the list of current records
// and removing all of those rows from this "intermediate" joining table.
$detach = array_values(array_intersect(
$this->newPivotQuery()->pluck($this->relatedPivotKey)->all(),
array_keys($records)
));
if (count($detach) > 0) {
$this->detach($detach, false);
$changes['detached'] = $this->castKeys($detach);
}
// Finally, for all of the records which were not "detached", we'll attach the
// records into the intermediate table. Then, we will add those attaches to
// this change list and get ready to return these results to the callers.
$attach = array_diff_key($records, array_flip($detach));
if (count($attach) > 0) {
$this->attach($attach, [], false);
$changes['attached'] = array_keys($attach);
}
// Once we have finished attaching or detaching the records, we will see if we
// have done any attaching or detaching, and if we have we will touch these
// relationships if they are configured to touch on any database updates.
if ($touch && (count($changes['attached']) ||
count($changes['detached']))) {
$this->touchIfTouching();
}
return $changes;
}
/**
* Sync the intermediate tables with a list of IDs without detaching.
*
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @return array
*/
public function syncWithoutDetaching($ids)
{
return $this->sync($ids, false);
}
/**
* Sync the intermediate tables with a list of IDs or collection of models.
*
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param bool $detaching
* @return array
*/
public function sync($ids, $detaching = true)
{
$changes = [
'attached' => [], 'detached' => [], 'updated' => [],
];
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->getCurrentlyAttachedPivots()
->pluck($this->relatedPivotKey)->all();
$detach = array_diff($current, array_keys(
$records = $this->formatRecordsList($this->parseIds($ids))
));
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// array of the new IDs given to the method which will complete the sync.
if ($detaching && count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = $this->castKeys($detach);
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
// Once we have finished attaching or detaching the records, we will see if we
// have done any attaching or detaching, and if we have we will touch these
// relationships if they are configured to touch on any database updates.
if (count($changes['attached']) ||
count($changes['updated'])) {
$this->touchIfTouching();
}
return $changes;
}
/**
* Format the sync / toggle record list so that it is keyed by ID.
*
* @param array $records
* @return array
*/
protected function formatRecordsList(array $records)
{
return collect($records)->mapWithKeys(function ($attributes, $id) {
if (! is_array($attributes)) {
[$id, $attributes] = [$attributes, []];
}
return [$id => $attributes];
})->all();
}
/**
* Attach all of the records that aren't in the given current records.
*
* @param array $records
* @param array $current
* @param bool $touch
* @return array
*/
protected function attachNew(array $records, array $current, $touch = true)
{
$changes = ['attached' => [], 'updated' => []];
foreach ($records as $id => $attributes) {
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
// record, otherwise, we will just update this existing record on this joining
// table, so that the developers will easily update these records pain free.
if (! in_array($id, $current)) {
$this->attach($id, $attributes, $touch);
$changes['attached'][] = $this->castKey($id);
}
// Now we'll try to update an existing pivot record with the attributes that were
// given to the method. If the model is actually updated we will add it to the
// list of updated pivot records so we return them back out to the consumer.
elseif (count($attributes) > 0 &&
$this->updateExistingPivot($id, $attributes, $touch)) {
$changes['updated'][] = $this->castKey($id);
}
}
return $changes;
}
/**
* Update an existing pivot record on the table.
*
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @return int
*/
public function updateExistingPivot($id, array $attributes, $touch = true)
{
if ($this->using &&
empty($this->pivotWheres) &&
empty($this->pivotWhereIns) &&
empty($this->pivotWhereNulls)) {
return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch);
}
if (in_array($this->updatedAt(), $this->pivotColumns)) {
$attributes = $this->addTimestampsToAttachment($attributes, true);
}
$updated = $this->newPivotStatementForId($this->parseId($id))->update(
$this->castAttributes($attributes)
);
if ($touch) {
$this->touchIfTouching();
}
return $updated;
}
/**
* Update an existing pivot record on the table via a custom class.
*
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @return int
*/
protected function updateExistingPivotUsingCustomClass($id, array $attributes, $touch)
{
$pivot = $this->getCurrentlyAttachedPivots()
->where($this->foreignPivotKey, $this->parent->{$this->parentKey})
->where($this->relatedPivotKey, $this->parseId($id))
->first();
$updated = $pivot ? $pivot->fill($attributes)->isDirty() : false;
if ($updated) {
$pivot->save();
}
if ($touch) {
$this->touchIfTouching();
}
return (int) $updated;
}
/**
* Attach a model to the parent.
*
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @return void
*/
public function attach($id, array $attributes = [], $touch = true)
{
if ($this->using) {
$this->attachUsingCustomClass($id, $attributes);
} else {
// Here we will insert the attachment records into the pivot table. Once we have
// inserted the records, we will touch the relationships if necessary and the
// function will return. We can parse the IDs before inserting the records.
$this->newPivotStatement()->insert($this->formatAttachRecords(
$this->parseIds($id), $attributes
));
}
if ($touch) {
$this->touchIfTouching();
}
}
/**
* Attach a model to the parent using a custom class.
*
* @param mixed $id
* @param array $attributes
* @return void
*/
protected function attachUsingCustomClass($id, array $attributes)
{
$records = $this->formatAttachRecords(
$this->parseIds($id), $attributes
);
foreach ($records as $record) {
$this->newPivot($record, false)->save();
}
}
/**
* Create an array of records to insert into the pivot table.
*
* @param array $ids
* @param array $attributes
* @return array
*/
protected function formatAttachRecords($ids, array $attributes)
{
$records = [];
$hasTimestamps = ($this->hasPivotColumn($this->createdAt()) ||
$this->hasPivotColumn($this->updatedAt()));
// To create the attachment records, we will simply spin through the IDs given
// and create a new record to insert for each ID. Each ID may actually be a
// key in the array, with extra attributes to be placed in other columns.
foreach ($ids as $key => $value) {
$records[] = $this->formatAttachRecord(
$key, $value, $attributes, $hasTimestamps
);
}
return $records;
}
/**
* Create a full attachment record payload.
*
* @param int $key
* @param mixed $value
* @param array $attributes
* @param bool $hasTimestamps
* @return array
*/
protected function formatAttachRecord($key, $value, $attributes, $hasTimestamps)
{
[$id, $attributes] = $this->extractAttachIdAndAttributes($key, $value, $attributes);
return array_merge(
$this->baseAttachRecord($id, $hasTimestamps), $this->castAttributes($attributes)
);
}
/**
* Get the attach record ID and extra attributes.
*
* @param mixed $key
* @param mixed $value
* @param array $attributes
* @return array
*/
protected function extractAttachIdAndAttributes($key, $value, array $attributes)
{
return is_array($value)
? [$key, array_merge($value, $attributes)]
: [$value, $attributes];
}
/**
* Create a new pivot attachment record.
*
* @param int $id
* @param bool $timed
* @return array
*/
protected function baseAttachRecord($id, $timed)
{
$record[$this->relatedPivotKey] = $id;
$record[$this->foreignPivotKey] = $this->parent->{$this->parentKey};
// If the record needs to have creation and update timestamps, we will make
// them by calling the parent model's "freshTimestamp" method which will
// provide us with a fresh timestamp in this model's preferred format.
if ($timed) {
$record = $this->addTimestampsToAttachment($record);
}
foreach ($this->pivotValues as $value) {
$record[$value['column']] = $value['value'];
}
return $record;
}
/**
* Set the creation and update timestamps on an attach record.
*
* @param array $record
* @param bool $exists
* @return array
*/
protected function addTimestampsToAttachment(array $record, $exists = false)
{
$fresh = $this->parent->freshTimestamp();
if ($this->using) {
$pivotModel = new $this->using;
$fresh = $fresh->format($pivotModel->getDateFormat());
}
if (! $exists && $this->hasPivotColumn($this->createdAt())) {
$record[$this->createdAt()] = $fresh;
}
if ($this->hasPivotColumn($this->updatedAt())) {
$record[$this->updatedAt()] = $fresh;
}
return $record;
}
/**
* Determine whether the given column is defined as a pivot column.
*
* @param string $column
* @return bool
*/
public function hasPivotColumn($column)
{
return in_array($column, $this->pivotColumns);
}
/**
* Detach models from the relationship.
*
* @param mixed $ids
* @param bool $touch
* @return int
*/
public function detach($ids = null, $touch = true)
{
if ($this->using &&
! empty($ids) &&
empty($this->pivotWheres) &&
empty($this->pivotWhereIns) &&
empty($this->pivotWhereNulls)) {
$results = $this->detachUsingCustomClass($ids);
} else {
$query = $this->newPivotQuery();
// If associated IDs were passed to the method we will only delete those
// associations, otherwise all of the association ties will be broken.
// We'll return the numbers of affected rows when we do the deletes.
if (! is_null($ids)) {
$ids = $this->parseIds($ids);
if (empty($ids)) {
return 0;
}
$query->whereIn($this->relatedPivotKey, (array) $ids);
}
// Once we have all of the conditions set on the statement, we are ready
// to run the delete on the pivot table. Then, if the touch parameter
// is true, we will go ahead and touch all related models to sync.
$results = $query->delete();
}
if ($touch) {
$this->touchIfTouching();
}
return $results;
}
/**
* Detach models from the relationship using a custom class.
*
* @param mixed $ids
* @return int
*/
protected function detachUsingCustomClass($ids)
{
$results = 0;
foreach ($this->parseIds($ids) as $id) {
$results += $this->newPivot([
$this->foreignPivotKey => $this->parent->{$this->parentKey},
$this->relatedPivotKey => $id,
], true)->delete();
}
return $results;
}
/**
* Get the pivot models that are currently attached.
*
* @return \Illuminate\Support\Collection
*/
protected function getCurrentlyAttachedPivots()
{
return $this->newPivotQuery()->get()->map(function ($record) {
$class = $this->using ? $this->using : Pivot::class;
$pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true);
return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
});
}
/**
* Create a new pivot model instance.
*
* @param array $attributes
* @param bool $exists
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newPivot(array $attributes = [], $exists = false)
{
$pivot = $this->related->newPivot(
$this->parent, $attributes, $this->table, $exists, $this->using
);
return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
}
/**
* Create a new existing pivot model instance.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newExistingPivot(array $attributes = [])
{
return $this->newPivot($attributes, true);
}
/**
* Get a new plain query builder for the pivot table.
*
* @return \Illuminate\Database\Query\Builder
*/
public function newPivotStatement()
{
return $this->query->getQuery()->newQuery()->from($this->table);
}
/**
* Get a new pivot statement for a given "other" ID.
*
* @param mixed $id
* @return \Illuminate\Database\Query\Builder
*/
public function newPivotStatementForId($id)
{
return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id));
}
/**
* Create a new query builder for the pivot table.
*
* @return \Illuminate\Database\Query\Builder
*/
public function newPivotQuery()
{
$query = $this->newPivotStatement();
foreach ($this->pivotWheres as $arguments) {
$query->where(...$arguments);
}
foreach ($this->pivotWhereIns as $arguments) {
$query->whereIn(...$arguments);
}
foreach ($this->pivotWhereNulls as $arguments) {
$query->whereNull(...$arguments);
}
return $query->where($this->foreignPivotKey, $this->parent->{$this->parentKey});
}
/**
* Set the columns on the pivot table to retrieve.
*
* @param array|mixed $columns
* @return $this
*/
public function withPivot($columns)
{
$this->pivotColumns = array_merge(
$this->pivotColumns, is_array($columns) ? $columns : func_get_args()
);
return $this;
}
/**
* Get all of the IDs from the given mixed value.
*
* @param mixed $value
* @return array
*/
protected function parseIds($value)
{
if ($value instanceof Model) {
return [$value->{$this->relatedKey}];
}
if ($value instanceof Collection) {
return $value->pluck($this->relatedKey)->all();
}
if ($value instanceof BaseCollection) {
return $value->toArray();
}
return (array) $value;
}
/**
* Get the ID from the given mixed value.
*
* @param mixed $value
* @return mixed
*/
protected function parseId($value)
{
return $value instanceof Model ? $value->{$this->relatedKey} : $value;
}
/**
* Cast the given keys to integers if they are numeric and string otherwise.
*
* @param array $keys
* @return array
*/
protected function castKeys(array $keys)
{
return array_map(function ($v) {
return $this->castKey($v);
}, $keys);
}
/**
* Cast the given key to convert to primary key type.
*
* @param mixed $key
* @return mixed
*/
protected function castKey($key)
{
return $this->getTypeSwapValue(
$this->related->getKeyType(),
$key
);
}
/**
* Cast the given pivot attributes.
*
* @param array $attributes
* @return array
*/
protected function castAttributes($attributes)
{
return $this->using
? $this->newPivot()->fill($attributes)->getAttributes()
: $attributes;
}
/**
* Converts a given value to a given type value.
*
* @param string $type
* @param mixed $value
* @return mixed
*/
protected function getTypeSwapValue($type, $value)
{
switch (strtolower($type)) {
case 'int':
case 'integer':
return (int) $value;
case 'real':
case 'float':
case 'double':
return (float) $value;
case 'string':
return (string) $value;
default:
return $value;
}
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Illuminate\Database\Eloquent\Relations\Concerns;
use Illuminate\Database\Eloquent\Model;
trait SupportsDefaultModels
{
/**
* Indicates if a default model instance should be used.
*
* Alternatively, may be a Closure or array.
*
* @var \Closure|array|bool
*/
protected $withDefault;
/**
* Make a new related instance for the given model.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model
*/
abstract protected function newRelatedInstanceFor(Model $parent);
/**
* Return a new model instance in case the relationship does not exist.
*
* @param \Closure|array|bool $callback
* @return $this
*/
public function withDefault($callback = true)
{
$this->withDefault = $callback;
return $this;
}
/**
* Get the default value for this relation.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model|null
*/
protected function getDefaultFor(Model $parent)
{
if (! $this->withDefault) {
return;
}
$instance = $this->newRelatedInstanceFor($parent);
if (is_callable($this->withDefault)) {
return call_user_func($this->withDefault, $instance, $parent) ?: $instance;
}
if (is_array($this->withDefault)) {
$instance->forceFill($this->withDefault);
}
return $instance;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
class HasMany extends HasOneOrMany
{
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return ! is_null($this->getParentKey())
? $this->query->get()
: $this->related->newCollection();
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchMany($models, $results, $relation);
}
}

View File

@ -0,0 +1,688 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\SoftDeletes;
class HasManyThrough extends Relation
{
/**
* The "through" parent model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $throughParent;
/**
* The far parent model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $farParent;
/**
* The near key on the relationship.
*
* @var string
*/
protected $firstKey;
/**
* The far key on the relationship.
*
* @var string
*/
protected $secondKey;
/**
* The local key on the relationship.
*
* @var string
*/
protected $localKey;
/**
* The local key on the intermediary model.
*
* @var string
*/
protected $secondLocalKey;
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new has many through relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $farParent
* @param \Illuminate\Database\Eloquent\Model $throughParent
* @param string $firstKey
* @param string $secondKey
* @param string $localKey
* @param string $secondLocalKey
* @return void
*/
public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
{
$this->localKey = $localKey;
$this->firstKey = $firstKey;
$this->secondKey = $secondKey;
$this->farParent = $farParent;
$this->throughParent = $throughParent;
$this->secondLocalKey = $secondLocalKey;
parent::__construct($query, $throughParent);
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
$localValue = $this->farParent[$this->localKey];
$this->performJoin();
if (static::$constraints) {
$this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue);
}
}
/**
* Set the join clause on the query.
*
* @param \Illuminate\Database\Eloquent\Builder|null $query
* @return void
*/
protected function performJoin(Builder $query = null)
{
$query = $query ?: $this->query;
$farKey = $this->getQualifiedFarKeyName();
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey);
if ($this->throughParentSoftDeletes()) {
$query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) {
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
});
}
}
/**
* Get the fully qualified parent key name.
*
* @return string
*/
public function getQualifiedParentKeyName()
{
return $this->parent->qualifyColumn($this->secondLocalKey);
}
/**
* Determine whether "through" parent of the relation uses Soft Deletes.
*
* @return bool
*/
public function throughParentSoftDeletes()
{
return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
}
/**
* Indicate that trashed "through" parents should be included in the query.
*
* @return $this
*/
public function withTrashedParents()
{
$this->query->withoutGlobalScope('SoftDeletableHasManyThrough');
return $this;
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
$whereIn = $this->whereInMethod($this->farParent, $this->localKey);
$this->query->{$whereIn}(
$this->getQualifiedFirstKeyName(), $this->getKeys($models, $this->localKey)
);
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
$dictionary = $this->buildDictionary($results);
// Once we have the dictionary we can simply spin through the parent models to
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
$model->setRelation(
$relation, $this->related->newCollection($dictionary[$key])
);
}
}
return $models;
}
/**
* Build model dictionary keyed by the relation's foreign key.
*
* @param \Illuminate\Database\Eloquent\Collection $results
* @return array
*/
protected function buildDictionary(Collection $results)
{
$dictionary = [];
// First we will create a dictionary of models keyed by the foreign key of the
// relationship as this will allow us to quickly access all of the related
// models without having to do nested looping which will be quite slow.
foreach ($results as $result) {
$dictionary[$result->laravel_through_key][] = $result;
}
return $dictionary;
}
/**
* Get the first related model record matching the attributes or instantiate it.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function firstOrNew(array $attributes)
{
if (is_null($instance = $this->where($attributes)->first())) {
$instance = $this->related->newInstance($attributes);
}
return $instance;
}
/**
* Create or update a related record matching the attributes, and fill it with values.
*
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public function updateOrCreate(array $attributes, array $values = [])
{
$instance = $this->firstOrNew($attributes);
$instance->fill($values)->save();
return $instance;
}
/**
* Add a basic where clause to the query, and return the first result.
*
* @param \Closure|string|array $column
* @param mixed $operator
* @param mixed $value
* @param string $boolean
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
{
return $this->where($column, $operator, $value, $boolean)->first();
}
/**
* Execute the query and get the first related model.
*
* @param array $columns
* @return mixed
*/
public function first($columns = ['*'])
{
$results = $this->take(1)->get($columns);
return count($results) > 0 ? $results->first() : null;
}
/**
* Execute the query and get the first result or throw an exception.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function firstOrFail($columns = ['*'])
{
if (! is_null($model = $this->first($columns))) {
return $model;
}
throw (new ModelNotFoundException)->setModel(get_class($this->related));
}
/**
* Find a related model by its primary key.
*
* @param mixed $id
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
*/
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
return $this->where(
$this->getRelated()->getQualifiedKeyName(), '=', $id
)->first($columns);
}
/**
* Find multiple related models by their primary keys.
*
* @param \Illuminate\Contracts\Support\Arrayable|array $ids
* @param array $columns
* @return \Illuminate\Database\Eloquent\Collection
*/
public function findMany($ids, $columns = ['*'])
{
$ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
if (empty($ids)) {
return $this->getRelated()->newCollection();
}
return $this->whereIn(
$this->getRelated()->getQualifiedKeyName(), $ids
)->get($columns);
}
/**
* Find a related model by its primary key or throw an exception.
*
* @param mixed $id
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findOrFail($id, $columns = ['*'])
{
$result = $this->find($id, $columns);
$id = $id instanceof Arrayable ? $id->toArray() : $id;
if (is_array($id)) {
if (count($result) === count(array_unique($id))) {
return $result;
}
} elseif (! is_null($result)) {
return $result;
}
throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
}
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return ! is_null($this->farParent->{$this->localKey})
? $this->get()
: $this->related->newCollection();
}
/**
* Execute the query as a "select" statement.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Collection
*/
public function get($columns = ['*'])
{
$builder = $this->prepareQueryBuilder($columns);
$models = $builder->getModels();
// If we actually found models we will also eager load any relationships that
// have been specified as needing to be eager loaded. This will solve the
// n + 1 query problem for the developer and also increase performance.
if (count($models) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
}
/**
* Get a paginator for the "select" statement.
*
* @param int|null $perPage
* @param array $columns
* @param string $pageName
* @param int $page
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
$this->query->addSelect($this->shouldSelect($columns));
return $this->query->paginate($perPage, $columns, $pageName, $page);
}
/**
* Paginate the given query into a simple paginator.
*
* @param int|null $perPage
* @param array $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Contracts\Pagination\Paginator
*/
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
$this->query->addSelect($this->shouldSelect($columns));
return $this->query->simplePaginate($perPage, $columns, $pageName, $page);
}
/**
* Set the select clause for the relation query.
*
* @param array $columns
* @return array
*/
protected function shouldSelect(array $columns = ['*'])
{
if ($columns == ['*']) {
$columns = [$this->related->getTable().'.*'];
}
return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
}
/**
* Chunk the results of the query.
*
* @param int $count
* @param callable $callback
* @return bool
*/
public function chunk($count, callable $callback)
{
return $this->prepareQueryBuilder()->chunk($count, $callback);
}
/**
* Chunk the results of a query by comparing numeric IDs.
*
* @param int $count
* @param callable $callback
* @param string|null $column
* @param string|null $alias
* @return bool
*/
public function chunkById($count, callable $callback, $column = null, $alias = null)
{
$column = $column ?? $this->getRelated()->getQualifiedKeyName();
$alias = $alias ?? $this->getRelated()->getKeyName();
return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias);
}
/**
* Get a generator for the given query.
*
* @return \Generator
*/
public function cursor()
{
return $this->prepareQueryBuilder()->cursor();
}
/**
* Execute a callback over each item while chunking.
*
* @param callable $callback
* @param int $count
* @return bool
*/
public function each(callable $callback, $count = 1000)
{
return $this->chunk($count, function ($results) use ($callback) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
return false;
}
}
});
}
/**
* Prepare the query builder for query execution.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function prepareQueryBuilder($columns = ['*'])
{
$builder = $this->query->applyScopes();
return $builder->addSelect(
$this->shouldSelect($builder->getQuery()->columns ? [] : $columns)
);
}
/**
* Add the constraints for a relationship query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
if ($parentQuery->getQuery()->from === $query->getQuery()->from) {
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
}
if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) {
return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns);
}
$this->performJoin($query);
return $query->select($columns)->whereColumn(
$this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName()
);
}
/**
* Add the constraints for a relationship query on the same table.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey);
if ($this->throughParentSoftDeletes()) {
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
}
$query->getModel()->setTable($hash);
return $query->select($columns)->whereColumn(
$parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName()
);
}
/**
* Add the constraints for a relationship query on the same table as the through parent.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash();
$query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName());
if ($this->throughParentSoftDeletes()) {
$query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn());
}
return $query->select($columns)->whereColumn(
$parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey
);
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Get the qualified foreign key on the related model.
*
* @return string
*/
public function getQualifiedFarKeyName()
{
return $this->getQualifiedForeignKeyName();
}
/**
* Get the foreign key on the "through" model.
*
* @return string
*/
public function getFirstKeyName()
{
return $this->firstKey;
}
/**
* Get the qualified foreign key on the "through" model.
*
* @return string
*/
public function getQualifiedFirstKeyName()
{
return $this->throughParent->qualifyColumn($this->firstKey);
}
/**
* Get the foreign key on the related model.
*
* @return string
*/
public function getForeignKeyName()
{
return $this->secondKey;
}
/**
* Get the qualified foreign key on the related model.
*
* @return string
*/
public function getQualifiedForeignKeyName()
{
return $this->related->qualifyColumn($this->secondKey);
}
/**
* Get the local key on the far parent model.
*
* @return string
*/
public function getLocalKeyName()
{
return $this->localKey;
}
/**
* Get the qualified local key on the far parent model.
*
* @return string
*/
public function getQualifiedLocalKeyName()
{
return $this->farParent->qualifyColumn($this->localKey);
}
/**
* Get the local key on the intermediary model.
*
* @return string
*/
public function getSecondLocalKeyName()
{
return $this->secondLocalKey;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
class HasOne extends HasOneOrMany
{
use SupportsDefaultModels;
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
if (is_null($this->getParentKey())) {
return $this->getDefaultFor($this->parent);
}
return $this->query->first() ?: $this->getDefaultFor($this->parent);
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->getDefaultFor($model));
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchOne($models, $results, $relation);
}
/**
* Make a new related instance for the given model.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model
*/
public function newRelatedInstanceFor(Model $parent)
{
return $this->related->newInstance()->setAttribute(
$this->getForeignKeyName(), $parent->{$this->localKey}
);
}
}

View File

@ -0,0 +1,437 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
abstract class HasOneOrMany extends Relation
{
/**
* The foreign key of the parent model.
*
* @var string
*/
protected $foreignKey;
/**
* The local key of the parent model.
*
* @var string
*/
protected $localKey;
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new has one or many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $localKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
{
$this->localKey = $localKey;
$this->foreignKey = $foreignKey;
parent::__construct($query, $parent);
}
/**
* Create and return an un-saved instance of the related model.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function make(array $attributes = [])
{
return tap($this->related->newInstance($attributes), function ($instance) {
$this->setForeignAttributesForCreate($instance);
});
}
/**
* Create and return an un-saved instances of the related models.
*
* @param iterable $records
* @return \Illuminate\Database\Eloquent\Collection
*/
public function makeMany($records)
{
$instances = $this->related->newCollection();
foreach ($records as $record) {
$instances->push($this->make($record));
}
return $instances;
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints) {
$this->query->where($this->foreignKey, '=', $this->getParentKey());
$this->query->whereNotNull($this->foreignKey);
}
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
$whereIn = $this->whereInMethod($this->parent, $this->localKey);
$this->query->{$whereIn}(
$this->foreignKey, $this->getKeys($models, $this->localKey)
);
}
/**
* Match the eagerly loaded results to their single parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function matchOne(array $models, Collection $results, $relation)
{
return $this->matchOneOrMany($models, $results, $relation, 'one');
}
/**
* Match the eagerly loaded results to their many parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function matchMany(array $models, Collection $results, $relation)
{
return $this->matchOneOrMany($models, $results, $relation, 'many');
}
/**
* Match the eagerly loaded results to their many parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @param string $type
* @return array
*/
protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
{
$dictionary = $this->buildDictionary($results);
// Once we have the dictionary we can simply spin through the parent models to
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
$model->setRelation(
$relation, $this->getRelationValue($dictionary, $key, $type)
);
}
}
return $models;
}
/**
* Get the value of a relationship by one or many type.
*
* @param array $dictionary
* @param string $key
* @param string $type
* @return mixed
*/
protected function getRelationValue(array $dictionary, $key, $type)
{
$value = $dictionary[$key];
return $type === 'one' ? reset($value) : $this->related->newCollection($value);
}
/**
* Build model dictionary keyed by the relation's foreign key.
*
* @param \Illuminate\Database\Eloquent\Collection $results
* @return array
*/
protected function buildDictionary(Collection $results)
{
$foreign = $this->getForeignKeyName();
return $results->mapToDictionary(function ($result) use ($foreign) {
return [$result->{$foreign} => $result];
})->all();
}
/**
* Find a model by its primary key or return new instance of the related model.
*
* @param mixed $id
* @param array $columns
* @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
*/
public function findOrNew($id, $columns = ['*'])
{
if (is_null($instance = $this->find($id, $columns))) {
$instance = $this->related->newInstance();
$this->setForeignAttributesForCreate($instance);
}
return $instance;
}
/**
* Get the first related model record matching the attributes or instantiate it.
*
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public function firstOrNew(array $attributes, array $values = [])
{
if (is_null($instance = $this->where($attributes)->first())) {
$instance = $this->related->newInstance($attributes + $values);
$this->setForeignAttributesForCreate($instance);
}
return $instance;
}
/**
* Get the first related record matching the attributes or create it.
*
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public function firstOrCreate(array $attributes, array $values = [])
{
if (is_null($instance = $this->where($attributes)->first())) {
$instance = $this->create($attributes + $values);
}
return $instance;
}
/**
* Create or update a related record matching the attributes, and fill it with values.
*
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public function updateOrCreate(array $attributes, array $values = [])
{
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
$instance->fill($values);
$instance->save();
});
}
/**
* Attach a model instance to the parent model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model|false
*/
public function save(Model $model)
{
$this->setForeignAttributesForCreate($model);
return $model->save() ? $model : false;
}
/**
* Attach a collection of models to the parent instance.
*
* @param iterable $models
* @return iterable
*/
public function saveMany($models)
{
foreach ($models as $model) {
$this->save($model);
}
return $models;
}
/**
* Create a new instance of the related model.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes = [])
{
return tap($this->related->newInstance($attributes), function ($instance) {
$this->setForeignAttributesForCreate($instance);
$instance->save();
});
}
/**
* Create a Collection of new instances of the related model.
*
* @param iterable $records
* @return \Illuminate\Database\Eloquent\Collection
*/
public function createMany(iterable $records)
{
$instances = $this->related->newCollection();
foreach ($records as $record) {
$instances->push($this->create($record));
}
return $instances;
}
/**
* Set the foreign ID for creating a related model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
protected function setForeignAttributesForCreate(Model $model)
{
$model->setAttribute($this->getForeignKeyName(), $this->getParentKey());
}
/**
* Add the constraints for a relationship query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
if ($query->getQuery()->from == $parentQuery->getQuery()->from) {
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
}
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
}
/**
* Add the constraints for a relationship query on the same table.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
{
$query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
$query->getModel()->setTable($hash);
return $query->select($columns)->whereColumn(
$this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->getForeignKeyName()
);
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Get the key for comparing against the parent key in "has" query.
*
* @return string
*/
public function getExistenceCompareKey()
{
return $this->getQualifiedForeignKeyName();
}
/**
* Get the key value of the parent's local key.
*
* @return mixed
*/
public function getParentKey()
{
return $this->parent->getAttribute($this->localKey);
}
/**
* Get the fully qualified parent key name.
*
* @return string
*/
public function getQualifiedParentKeyName()
{
return $this->parent->qualifyColumn($this->localKey);
}
/**
* Get the plain foreign key.
*
* @return string
*/
public function getForeignKeyName()
{
$segments = explode('.', $this->getQualifiedForeignKeyName());
return end($segments);
}
/**
* Get the foreign key for the relationship.
*
* @return string
*/
public function getQualifiedForeignKeyName()
{
return $this->foreignKey;
}
/**
* Get the local key for the relationship.
*
* @return string
*/
public function getLocalKeyName()
{
return $this->localKey;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
class HasOneThrough extends HasManyThrough
{
use SupportsDefaultModels;
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->first() ?: $this->getDefaultFor($this->farParent);
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->getDefaultFor($model));
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
$dictionary = $this->buildDictionary($results);
// Once we have the dictionary we can simply spin through the parent models to
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
$value = $dictionary[$key];
$model->setRelation(
$relation, reset($value)
);
}
}
return $models;
}
/**
* Make a new related instance for the given model.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model
*/
public function newRelatedInstanceFor(Model $parent)
{
return $this->related->newInstance();
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
class MorphMany extends MorphOneOrMany
{
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return ! is_null($this->getParentKey())
? $this->query->get()
: $this->related->newCollection();
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchMany($models, $results, $relation);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
class MorphOne extends MorphOneOrMany
{
use SupportsDefaultModels;
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
if (is_null($this->getParentKey())) {
return $this->getDefaultFor($this->parent);
}
return $this->query->first() ?: $this->getDefaultFor($this->parent);
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->getDefaultFor($model));
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchOne($models, $results, $relation);
}
/**
* Make a new related instance for the given model.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model
*/
public function newRelatedInstanceFor(Model $parent)
{
return $this->related->newInstance()
->setAttribute($this->getForeignKeyName(), $parent->{$this->localKey})
->setAttribute($this->getMorphType(), $this->morphClass);
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
abstract class MorphOneOrMany extends HasOneOrMany
{
/**
* The foreign key type for the relationship.
*
* @var string
*/
protected $morphType;
/**
* The class name of the parent model.
*
* @var string
*/
protected $morphClass;
/**
* Create a new morph one or many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
{
$this->morphType = $type;
$this->morphClass = $parent->getMorphClass();
parent::__construct($query, $parent, $id, $localKey);
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints) {
parent::addConstraints();
$this->query->where($this->morphType, $this->morphClass);
}
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
parent::addEagerConstraints($models);
$this->query->where($this->morphType, $this->morphClass);
}
/**
* Set the foreign ID and type for creating a related model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
protected function setForeignAttributesForCreate(Model $model)
{
$model->{$this->getForeignKeyName()} = $this->getParentKey();
$model->{$this->getMorphType()} = $this->morphClass;
}
/**
* Get the relationship query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where(
$query->qualifyColumn($this->getMorphType()), $this->morphClass
);
}
/**
* Get the foreign key "type" name.
*
* @return string
*/
public function getQualifiedMorphType()
{
return $this->morphType;
}
/**
* Get the plain morph type name without the table.
*
* @return string
*/
public function getMorphType()
{
return last(explode('.', $this->morphType));
}
/**
* Get the class name of the parent model.
*
* @return string
*/
public function getMorphClass()
{
return $this->morphClass;
}
}

View File

@ -0,0 +1,162 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
class MorphPivot extends Pivot
{
/**
* The type of the polymorphic relation.
*
* Explicitly define this so it's not included in saved attributes.
*
* @var string
*/
protected $morphType;
/**
* The value of the polymorphic relation.
*
* Explicitly define this so it's not included in saved attributes.
*
* @var string
*/
protected $morphClass;
/**
* Set the keys for a save update query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery(Builder $query)
{
$query->where($this->morphType, $this->morphClass);
return parent::setKeysForSaveQuery($query);
}
/**
* Delete the pivot model record from the database.
*
* @return int
*/
public function delete()
{
if (isset($this->attributes[$this->getKeyName()])) {
return (int) parent::delete();
}
if ($this->fireModelEvent('deleting') === false) {
return 0;
}
$query = $this->getDeleteQuery();
$query->where($this->morphType, $this->morphClass);
return tap($query->delete(), function () {
$this->fireModelEvent('deleted', false);
});
}
/**
* Set the morph type for the pivot.
*
* @param string $morphType
* @return $this
*/
public function setMorphType($morphType)
{
$this->morphType = $morphType;
return $this;
}
/**
* Set the morph class for the pivot.
*
* @param string $morphClass
* @return \Illuminate\Database\Eloquent\Relations\MorphPivot
*/
public function setMorphClass($morphClass)
{
$this->morphClass = $morphClass;
return $this;
}
/**
* Get the queueable identity for the entity.
*
* @return mixed
*/
public function getQueueableId()
{
if (isset($this->attributes[$this->getKeyName()])) {
return $this->getKey();
}
return sprintf(
'%s:%s:%s:%s:%s:%s',
$this->foreignKey, $this->getAttribute($this->foreignKey),
$this->relatedKey, $this->getAttribute($this->relatedKey),
$this->morphType, $this->morphClass
);
}
/**
* Get a new query to restore one or more models by their queueable IDs.
*
* @param array|int $ids
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newQueryForRestoration($ids)
{
if (is_array($ids)) {
return $this->newQueryForCollectionRestoration($ids);
}
if (! Str::contains($ids, ':')) {
return parent::newQueryForRestoration($ids);
}
$segments = explode(':', $ids);
return $this->newQueryWithoutScopes()
->where($segments[0], $segments[1])
->where($segments[2], $segments[3])
->where($segments[4], $segments[5]);
}
/**
* Get a new query to restore multiple models by their queueable IDs.
*
* @param array $ids
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function newQueryForCollectionRestoration(array $ids)
{
$ids = array_values($ids);
if (! Str::contains($ids[0], ':')) {
return parent::newQueryForRestoration($ids);
}
$query = $this->newQueryWithoutScopes();
foreach ($ids as $id) {
$segments = explode(':', $id);
$query->orWhere(function ($query) use ($segments) {
return $query->where($segments[0], $segments[1])
->where($segments[2], $segments[3])
->where($segments[4], $segments[5]);
});
}
return $query;
}
}

View File

@ -0,0 +1,353 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use BadMethodCallException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class MorphTo extends BelongsTo
{
/**
* The type of the polymorphic relation.
*
* @var string
*/
protected $morphType;
/**
* The models whose relations are being eager loaded.
*
* @var \Illuminate\Database\Eloquent\Collection
*/
protected $models;
/**
* All of the models keyed by ID.
*
* @var array
*/
protected $dictionary = [];
/**
* A buffer of dynamic calls to query macros.
*
* @var array
*/
protected $macroBuffer = [];
/**
* A map of relations to load for each individual morph type.
*
* @var array
*/
protected $morphableEagerLoads = [];
/**
* A map of relationship counts to load for each individual morph type.
*
* @var array
*/
protected $morphableEagerLoadCounts = [];
/**
* Create a new morph to relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $ownerKey
* @param string $type
* @param string $relation
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
{
$this->morphType = $type;
parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation);
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
$this->buildDictionary($this->models = Collection::make($models));
}
/**
* Build a dictionary with the models.
*
* @param \Illuminate\Database\Eloquent\Collection $models
* @return void
*/
protected function buildDictionary(Collection $models)
{
foreach ($models as $model) {
if ($model->{$this->morphType}) {
$this->dictionary[$model->{$this->morphType}][$model->{$this->foreignKey}][] = $model;
}
}
}
/**
* Get the results of the relationship.
*
* Called via eager load method of Eloquent query builder.
*
* @return mixed
*/
public function getEager()
{
foreach (array_keys($this->dictionary) as $type) {
$this->matchToMorphParents($type, $this->getResultsByType($type));
}
return $this->models;
}
/**
* Get all of the relation results for a type.
*
* @param string $type
* @return \Illuminate\Database\Eloquent\Collection
*/
protected function getResultsByType($type)
{
$instance = $this->createModelByType($type);
$ownerKey = $this->ownerKey ?? $instance->getKeyName();
$query = $this->replayMacros($instance->newQuery())
->mergeConstraintsFrom($this->getQuery())
->with(array_merge(
$this->getQuery()->getEagerLoads(),
(array) ($this->morphableEagerLoads[get_class($instance)] ?? [])
))
->withCount(
(array) ($this->morphableEagerLoadCounts[get_class($instance)] ?? [])
);
$whereIn = $this->whereInMethod($instance, $ownerKey);
return $query->{$whereIn}(
$instance->getTable().'.'.$ownerKey, $this->gatherKeysByType($type)
)->get();
}
/**
* Gather all of the foreign keys for a given type.
*
* @param string $type
* @return array
*/
protected function gatherKeysByType($type)
{
return array_keys($this->dictionary[$type]);
}
/**
* Create a new model instance by type.
*
* @param string $type
* @return \Illuminate\Database\Eloquent\Model
*/
public function createModelByType($type)
{
$class = Model::getActualClassNameForMorph($type);
return tap(new $class, function ($instance) {
if (! $instance->getConnectionName()) {
$instance->setConnection($this->getConnection()->getName());
}
});
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $models;
}
/**
* Match the results for a given type to their parents.
*
* @param string $type
* @param \Illuminate\Database\Eloquent\Collection $results
* @return void
*/
protected function matchToMorphParents($type, Collection $results)
{
foreach ($results as $result) {
$ownerKey = ! is_null($this->ownerKey) ? $result->{$this->ownerKey} : $result->getKey();
if (isset($this->dictionary[$type][$ownerKey])) {
foreach ($this->dictionary[$type][$ownerKey] as $model) {
$model->setRelation($this->relationName, $result);
}
}
}
}
/**
* Associate the model instance to the given parent.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function associate($model)
{
$this->parent->setAttribute(
$this->foreignKey, $model instanceof Model ? $model->getKey() : null
);
$this->parent->setAttribute(
$this->morphType, $model instanceof Model ? $model->getMorphClass() : null
);
return $this->parent->setRelation($this->relationName, $model);
}
/**
* Dissociate previously associated model from the given parent.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function dissociate()
{
$this->parent->setAttribute($this->foreignKey, null);
$this->parent->setAttribute($this->morphType, null);
return $this->parent->setRelation($this->relationName, null);
}
/**
* Touch all of the related models for the relationship.
*
* @return void
*/
public function touch()
{
if (! is_null($this->child->{$this->foreignKey})) {
parent::touch();
}
}
/**
* Make a new related instance for the given model.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @return \Illuminate\Database\Eloquent\Model
*/
protected function newRelatedInstanceFor(Model $parent)
{
return $parent->{$this->getRelationName()}()->getRelated()->newInstance();
}
/**
* Get the foreign key "type" name.
*
* @return string
*/
public function getMorphType()
{
return $this->morphType;
}
/**
* Get the dictionary used by the relationship.
*
* @return array
*/
public function getDictionary()
{
return $this->dictionary;
}
/**
* Specify which relations to load for a given morph type.
*
* @param array $with
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphWith(array $with)
{
$this->morphableEagerLoads = array_merge(
$this->morphableEagerLoads, $with
);
return $this;
}
/**
* Specify which relationship counts to load for a given morph type.
*
* @param array $withCount
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphWithCount(array $withCount)
{
$this->morphableEagerLoadCounts = array_merge(
$this->morphableEagerLoadCounts, $withCount
);
return $this;
}
/**
* Replay stored macro calls on the actual related instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function replayMacros(Builder $query)
{
foreach ($this->macroBuffer as $macro) {
$query->{$macro['method']}(...$macro['parameters']);
}
return $query;
}
/**
* Handle dynamic method calls to the relationship.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
try {
$result = parent::__call($method, $parameters);
if (in_array($method, ['select', 'selectRaw', 'selectSub', 'addSelect', 'withoutGlobalScopes'])) {
$this->macroBuffer[] = compact('method', 'parameters');
}
return $result;
}
// If we tried to call a method that does not exist on the parent Builder instance,
// we'll assume that we want to call a query macro (e.g. withTrashed) that only
// exists on related models. We will just store the call and replay it later.
catch (BadMethodCallException $e) {
$this->macroBuffer[] = compact('method', 'parameters');
return $this;
}
}
}

View File

@ -0,0 +1,209 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
class MorphToMany extends BelongsToMany
{
/**
* The type of the polymorphic relation.
*
* @var string
*/
protected $morphType;
/**
* The class name of the morph type constraint.
*
* @var string
*/
protected $morphClass;
/**
* Indicates if we are connecting the inverse of the relation.
*
* This primarily affects the morphClass constraint.
*
* @var bool
*/
protected $inverse;
/**
* Create a new morph to many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $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 void
*/
public function __construct(Builder $query, Model $parent, $name, $table, $foreignPivotKey,
$relatedPivotKey, $parentKey, $relatedKey, $relationName = null, $inverse = false)
{
$this->inverse = $inverse;
$this->morphType = $name.'_type';
$this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass();
parent::__construct(
$query, $parent, $table, $foreignPivotKey,
$relatedPivotKey, $parentKey, $relatedKey, $relationName
);
}
/**
* Set the where clause for the relation query.
*
* @return $this
*/
protected function addWhereConstraints()
{
parent::addWhereConstraints();
$this->query->where($this->table.'.'.$this->morphType, $this->morphClass);
return $this;
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
parent::addEagerConstraints($models);
$this->query->where($this->table.'.'.$this->morphType, $this->morphClass);
}
/**
* Create a new pivot attachment record.
*
* @param int $id
* @param bool $timed
* @return array
*/
protected function baseAttachRecord($id, $timed)
{
return Arr::add(
parent::baseAttachRecord($id, $timed), $this->morphType, $this->morphClass
);
}
/**
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where(
$this->table.'.'.$this->morphType, $this->morphClass
);
}
/**
* Get the pivot models that are currently attached.
*
* @return \Illuminate\Support\Collection
*/
protected function getCurrentlyAttachedPivots()
{
return parent::getCurrentlyAttachedPivots()->map(function ($record) {
return $record instanceof MorphPivot
? $record->setMorphType($this->morphType)
->setMorphClass($this->morphClass)
: $record;
});
}
/**
* Create a new query builder for the pivot table.
*
* @return \Illuminate\Database\Query\Builder
*/
public function newPivotQuery()
{
return parent::newPivotQuery()->where($this->morphType, $this->morphClass);
}
/**
* Create a new pivot model instance.
*
* @param array $attributes
* @param bool $exists
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newPivot(array $attributes = [], $exists = false)
{
$using = $this->using;
$pivot = $using ? $using::fromRawAttributes($this->parent, $attributes, $this->table, $exists)
: MorphPivot::fromAttributes($this->parent, $attributes, $this->table, $exists);
$pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey)
->setMorphType($this->morphType)
->setMorphClass($this->morphClass);
return $pivot;
}
/**
* Get the pivot columns for the relation.
*
* "pivot_" is prefixed at each column for easy removal later.
*
* @return array
*/
protected function aliasedPivotColumns()
{
$defaults = [$this->foreignPivotKey, $this->relatedPivotKey, $this->morphType];
return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
return $this->table.'.'.$column.' as pivot_'.$column;
})->unique()->all();
}
/**
* Get the foreign key "type" name.
*
* @return string
*/
public function getMorphType()
{
return $this->morphType;
}
/**
* Get the class name of the parent model.
*
* @return string
*/
public function getMorphClass()
{
return $this->morphClass;
}
/**
* Get the indicator for a reverse relationship.
*
* @return bool
*/
public function getInverse()
{
return $this->inverse;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
class Pivot extends Model
{
use AsPivot;
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];
}

View File

@ -0,0 +1,403 @@
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Traits\Macroable;
/**
* @mixin \Illuminate\Database\Eloquent\Builder
*/
abstract class Relation
{
use ForwardsCalls, Macroable {
__call as macroCall;
}
/**
* The Eloquent query builder instance.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $query;
/**
* The parent model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $parent;
/**
* The related model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $related;
/**
* Indicates if the relation is adding constraints.
*
* @var bool
*/
protected static $constraints = true;
/**
* An array to map class names to their morph names in database.
*
* @var array
*/
public static $morphMap = [];
/**
* Create a new relation instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @return void
*/
public function __construct(Builder $query, Model $parent)
{
$this->query = $query;
$this->parent = $parent;
$this->related = $query->getModel();
$this->addConstraints();
}
/**
* Run a callback with constraints disabled on the relation.
*
* @param \Closure $callback
* @return mixed
*/
public static function noConstraints(Closure $callback)
{
$previous = static::$constraints;
static::$constraints = false;
// When resetting the relation where clause, we want to shift the first element
// off of the bindings, leaving only the constraints that the developers put
// as "extra" on the relationships, and not original relation constraints.
try {
return $callback();
} finally {
static::$constraints = $previous;
}
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
abstract public function addConstraints();
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
abstract public function addEagerConstraints(array $models);
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return array
*/
abstract public function initRelation(array $models, $relation);
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
abstract public function match(array $models, Collection $results, $relation);
/**
* Get the results of the relationship.
*
* @return mixed
*/
abstract public function getResults();
/**
* Get the relationship for eager loading.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getEager()
{
return $this->get();
}
/**
* Execute the query as a "select" statement.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Collection
*/
public function get($columns = ['*'])
{
return $this->query->get($columns);
}
/**
* Touch all of the related models for the relationship.
*
* @return void
*/
public function touch()
{
$model = $this->getRelated();
if (! $model::isIgnoringTouch()) {
$this->rawUpdate([
$model->getUpdatedAtColumn() => $model->freshTimestampString(),
]);
}
}
/**
* Run a raw update against the base query.
*
* @param array $attributes
* @return int
*/
public function rawUpdate(array $attributes = [])
{
return $this->query->withoutGlobalScopes()->update($attributes);
}
/**
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery)
{
return $this->getRelationExistenceQuery(
$query, $parentQuery, new Expression('count(*)')
)->setBindings([], 'select');
}
/**
* Add the constraints for an internal relationship existence query.
*
* Essentially, these queries compare on column names like whereColumn.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
return $query->select($columns)->whereColumn(
$this->getQualifiedParentKeyName(), '=', $this->getExistenceCompareKey()
);
}
/**
* Get all of the primary keys for an array of models.
*
* @param array $models
* @param string|null $key
* @return array
*/
protected function getKeys(array $models, $key = null)
{
return collect($models)->map(function ($value) use ($key) {
return $key ? $value->getAttribute($key) : $value->getKey();
})->values()->unique(null, true)->sort()->all();
}
/**
* Get the underlying query for the relation.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getQuery()
{
return $this->query;
}
/**
* Get the base query builder driving the Eloquent builder.
*
* @return \Illuminate\Database\Query\Builder
*/
public function getBaseQuery()
{
return $this->query->getQuery();
}
/**
* Get the parent model of the relation.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function getParent()
{
return $this->parent;
}
/**
* Get the fully qualified parent key name.
*
* @return string
*/
public function getQualifiedParentKeyName()
{
return $this->parent->getQualifiedKeyName();
}
/**
* Get the related model of the relation.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function getRelated()
{
return $this->related;
}
/**
* Get the name of the "created at" column.
*
* @return string
*/
public function createdAt()
{
return $this->parent->getCreatedAtColumn();
}
/**
* Get the name of the "updated at" column.
*
* @return string
*/
public function updatedAt()
{
return $this->parent->getUpdatedAtColumn();
}
/**
* Get the name of the related model's "updated at" column.
*
* @return string
*/
public function relatedUpdatedAt()
{
return $this->related->getUpdatedAtColumn();
}
/**
* Get the name of the "where in" method for eager loading.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @return string
*/
protected function whereInMethod(Model $model, $key)
{
return $model->getKeyName() === last(explode('.', $key))
&& in_array($model->getKeyType(), ['int', 'integer'])
? 'whereIntegerInRaw'
: 'whereIn';
}
/**
* Set or get the morph map for polymorphic relations.
*
* @param array|null $map
* @param bool $merge
* @return array
*/
public static function morphMap(array $map = null, $merge = true)
{
$map = static::buildMorphMapFromModels($map);
if (is_array($map)) {
static::$morphMap = $merge && static::$morphMap
? $map + static::$morphMap : $map;
}
return static::$morphMap;
}
/**
* Builds a table-keyed array from model class names.
*
* @param string[]|null $models
* @return array|null
*/
protected static function buildMorphMapFromModels(array $models = null)
{
if (is_null($models) || Arr::isAssoc($models)) {
return $models;
}
return array_combine(array_map(function ($model) {
return (new $model)->getTable();
}, $models), $models);
}
/**
* Get the model associated with a custom polymorphic type.
*
* @param string $alias
* @return string|null
*/
public static function getMorphedModel($alias)
{
return static::$morphMap[$alias] ?? null;
}
/**
* Handle dynamic method calls to the relationship.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
$result = $this->forwardCallTo($this->query, $method, $parameters);
if ($result === $this->query) {
return $this;
}
return $result;
}
/**
* Force a clone of the underlying query builder when cloning.
*
* @return void
*/
public function __clone()
{
$this->query = clone $this->query;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Illuminate\Database\Eloquent;
interface Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model);
}

View File

@ -0,0 +1,199 @@
<?php
namespace Illuminate\Database\Eloquent;
/**
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withTrashed()
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyTrashed()
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutTrashed()
*/
trait SoftDeletes
{
/**
* Indicates if the model is currently force deleting.
*
* @var bool
*/
protected $forceDeleting = false;
/**
* Boot the soft deleting trait for a model.
*
* @return void
*/
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingScope);
}
/**
* Initialize the soft deleting trait for an instance.
*
* @return void
*/
public function initializeSoftDeletes()
{
$this->dates[] = $this->getDeletedAtColumn();
}
/**
* Force a hard delete on a soft deleted model.
*
* @return bool|null
*/
public function forceDelete()
{
$this->forceDeleting = true;
return tap($this->delete(), function ($deleted) {
$this->forceDeleting = false;
if ($deleted) {
$this->fireModelEvent('forceDeleted', false);
}
});
}
/**
* Perform the actual delete query on this model instance.
*
* @return mixed
*/
protected function performDeleteOnModel()
{
if ($this->forceDeleting) {
$this->exists = false;
return $this->setKeysForSaveQuery($this->newModelQuery())->forceDelete();
}
return $this->runSoftDelete();
}
/**
* Perform the actual delete query on this model instance.
*
* @return void
*/
protected function runSoftDelete()
{
$query = $this->setKeysForSaveQuery($this->newModelQuery());
$time = $this->freshTimestamp();
$columns = [$this->getDeletedAtColumn() => $this->fromDateTime($time)];
$this->{$this->getDeletedAtColumn()} = $time;
if ($this->timestamps && ! is_null($this->getUpdatedAtColumn())) {
$this->{$this->getUpdatedAtColumn()} = $time;
$columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time);
}
$query->update($columns);
$this->syncOriginalAttributes(array_keys($columns));
}
/**
* Restore a soft-deleted model instance.
*
* @return bool|null
*/
public function restore()
{
// If the restoring event does not return false, we will proceed with this
// restore operation. Otherwise, we bail out so the developer will stop
// the restore totally. We will clear the deleted timestamp and save.
if ($this->fireModelEvent('restoring') === false) {
return false;
}
$this->{$this->getDeletedAtColumn()} = null;
// Once we have saved the model, we will fire the "restored" event so this
// developer will do anything they need to after a restore operation is
// totally finished. Then we will return the result of the save call.
$this->exists = true;
$result = $this->save();
$this->fireModelEvent('restored', false);
return $result;
}
/**
* Determine if the model instance has been soft-deleted.
*
* @return bool
*/
public function trashed()
{
return ! is_null($this->{$this->getDeletedAtColumn()});
}
/**
* Register a "restoring" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function restoring($callback)
{
static::registerModelEvent('restoring', $callback);
}
/**
* Register a "restored" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function restored($callback)
{
static::registerModelEvent('restored', $callback);
}
/**
* Register a "forceDeleted" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function forceDeleted($callback)
{
static::registerModelEvent('forceDeleted', $callback);
}
/**
* Determine if the model is currently force deleting.
*
* @return bool
*/
public function isForceDeleting()
{
return $this->forceDeleting;
}
/**
* Get the name of the "deleted at" column.
*
* @return string
*/
public function getDeletedAtColumn()
{
return defined('static::DELETED_AT') ? static::DELETED_AT : 'deleted_at';
}
/**
* Get the fully qualified "deleted at" column.
*
* @return string
*/
public function getQualifiedDeletedAtColumn()
{
return $this->qualifyColumn($this->getDeletedAtColumn());
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace Illuminate\Database\Eloquent;
class SoftDeletingScope implements Scope
{
/**
* All of the extensions to be added to the builder.
*
* @var array
*/
protected $extensions = ['Restore', 'WithTrashed', 'WithoutTrashed', 'OnlyTrashed'];
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->whereNull($model->getQualifiedDeletedAtColumn());
}
/**
* Extend the query builder with the needed functions.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function extend(Builder $builder)
{
foreach ($this->extensions as $extension) {
$this->{"add{$extension}"}($builder);
}
$builder->onDelete(function (Builder $builder) {
$column = $this->getDeletedAtColumn($builder);
return $builder->update([
$column => $builder->getModel()->freshTimestampString(),
]);
});
}
/**
* Get the "deleted at" column for the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return string
*/
protected function getDeletedAtColumn(Builder $builder)
{
if (count((array) $builder->getQuery()->joins) > 0) {
return $builder->getModel()->getQualifiedDeletedAtColumn();
}
return $builder->getModel()->getDeletedAtColumn();
}
/**
* Add the restore extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addRestore(Builder $builder)
{
$builder->macro('restore', function (Builder $builder) {
$builder->withTrashed();
return $builder->update([$builder->getModel()->getDeletedAtColumn() => null]);
});
}
/**
* Add the with-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addWithTrashed(Builder $builder)
{
$builder->macro('withTrashed', function (Builder $builder, $withTrashed = true) {
if (! $withTrashed) {
return $builder->withoutTrashed();
}
return $builder->withoutGlobalScope($this);
});
}
/**
* Add the without-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addWithoutTrashed(Builder $builder)
{
$builder->macro('withoutTrashed', function (Builder $builder) {
$model = $builder->getModel();
$builder->withoutGlobalScope($this)->whereNull(
$model->getQualifiedDeletedAtColumn()
);
return $builder;
});
}
/**
* Add the only-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addOnlyTrashed(Builder $builder)
{
$builder->macro('onlyTrashed', function (Builder $builder) {
$model = $builder->getModel();
$builder->withoutGlobalScope($this)->whereNotNull(
$model->getQualifiedDeletedAtColumn()
);
return $builder;
});
}
}