Upgrade framework
This commit is contained in:
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace Illuminate\Database\Capsule;
|
||||
|
||||
use PDO;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Support\Traits\CapsuleManagerTrait;
|
||||
use Illuminate\Database\Eloquent\Model as Eloquent;
|
||||
use Illuminate\Database\Connectors\ConnectionFactory;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use Illuminate\Database\Eloquent\Model as Eloquent;
|
||||
use Illuminate\Support\Traits\CapsuleManagerTrait;
|
||||
use PDO;
|
||||
|
||||
class Manager
|
||||
{
|
||||
@@ -66,7 +66,7 @@ class Manager
|
||||
/**
|
||||
* Get a connection instance from the global manager.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public static function connection($connection = null)
|
||||
@@ -77,19 +77,20 @@ class Manager
|
||||
/**
|
||||
* Get a fluent query builder instance.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $connection
|
||||
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
|
||||
* @param string|null $as
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public static function table($table, $connection = null)
|
||||
public static function table($table, $as = null, $connection = null)
|
||||
{
|
||||
return static::$instance->connection($connection)->table($table);
|
||||
return static::$instance->connection($connection)->table($table, $as);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a schema builder instance.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Schema\Builder
|
||||
*/
|
||||
public static function schema($connection = null)
|
||||
@@ -100,7 +101,7 @@ class Manager
|
||||
/**
|
||||
* Get a registered connection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function getConnection($name = null)
|
||||
@@ -111,7 +112,7 @@ class Manager
|
||||
/**
|
||||
* Register a connection with the manager.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $config
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
@@ -191,7 +192,7 @@ class Manager
|
||||
* Dynamically pass methods to the default connection.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic($method, $parameters)
|
||||
|
||||
29
vendor/laravel/framework/src/Illuminate/Database/ClassMorphViolationException.php
vendored
Normal file
29
vendor/laravel/framework/src/Illuminate/Database/ClassMorphViolationException.php
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ClassMorphViolationException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* The name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param object $model
|
||||
*/
|
||||
public function __construct($model)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
parent::__construct("No morph map defined for model [{$class}].");
|
||||
|
||||
$this->model = $class;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,25 @@
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\MultipleRecordsFoundException;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Database\RecordsNotFoundException;
|
||||
use Illuminate\Pagination\Cursor;
|
||||
use Illuminate\Pagination\CursorPaginator;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\Conditionable;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
trait BuildsQueries
|
||||
{
|
||||
use Conditionable;
|
||||
|
||||
/**
|
||||
* Chunk the results of the query.
|
||||
*
|
||||
@@ -36,22 +50,46 @@ trait BuildsQueries
|
||||
// On each chunk result set, we will pass them to the callback and then let the
|
||||
// developer take care of everything within the callback, which allows us to
|
||||
// keep the memory low for spinning through large result sets for working.
|
||||
if ($callback($results) === false) {
|
||||
if ($callback($results, $page) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($results);
|
||||
|
||||
$page++;
|
||||
} while ($countResults == $count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a map over each item while chunking.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function chunkMap(callable $callback, $count = 1000)
|
||||
{
|
||||
$collection = Collection::make();
|
||||
|
||||
$this->chunk($count, function ($items) use ($collection, $callback) {
|
||||
$items->each(function ($item) use ($collection, $callback) {
|
||||
$collection->push($callback($item));
|
||||
});
|
||||
});
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item while chunking.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @return bool
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function each(callable $callback, $count = 1000)
|
||||
{
|
||||
@@ -64,11 +102,194 @@ trait BuildsQueries
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk the results of a query by comparing 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 ??= $this->defaultKeyName();
|
||||
|
||||
$alias ??= $column;
|
||||
|
||||
$lastId = null;
|
||||
|
||||
$page = 1;
|
||||
|
||||
do {
|
||||
$clone = clone $this;
|
||||
|
||||
// We'll execute the query for the given page and get the results. If there are
|
||||
// no results we can just break and return from here. When there are results
|
||||
// we will call the callback with the current chunk of these results here.
|
||||
$results = $clone->forPageAfterId($count, $lastId, $column)->get();
|
||||
|
||||
$countResults = $results->count();
|
||||
|
||||
if ($countResults == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// On each chunk result set, we will pass them to the callback and then let the
|
||||
// developer take care of everything within the callback, which allows us to
|
||||
// keep the memory low for spinning through large result sets for working.
|
||||
if ($callback($results, $page) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastId = data_get($results->last(), $alias);
|
||||
|
||||
if ($lastId === null) {
|
||||
throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result.");
|
||||
}
|
||||
|
||||
unset($results);
|
||||
|
||||
$page++;
|
||||
} while ($countResults == $count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item while chunking by ID.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return bool
|
||||
*/
|
||||
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
|
||||
{
|
||||
return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
|
||||
foreach ($results as $key => $value) {
|
||||
if ($callback($value, (($page - 1) * $count) + $key) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunks of the given size.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function lazy($chunkSize = 1000)
|
||||
{
|
||||
if ($chunkSize < 1) {
|
||||
throw new InvalidArgumentException('The chunk size should be at least 1');
|
||||
}
|
||||
|
||||
$this->enforceOrderBy();
|
||||
|
||||
return LazyCollection::make(function () use ($chunkSize) {
|
||||
$page = 1;
|
||||
|
||||
while (true) {
|
||||
$results = $this->forPage($page++, $chunkSize)->get();
|
||||
|
||||
foreach ($results as $result) {
|
||||
yield $result;
|
||||
}
|
||||
|
||||
if ($results->count() < $chunkSize) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
|
||||
{
|
||||
return $this->orderedLazyById($chunkSize, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs in descending order.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
|
||||
{
|
||||
return $this->orderedLazyById($chunkSize, $column, $alias, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs in a given order.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @param bool $descending
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false)
|
||||
{
|
||||
if ($chunkSize < 1) {
|
||||
throw new InvalidArgumentException('The chunk size should be at least 1');
|
||||
}
|
||||
|
||||
$column ??= $this->defaultKeyName();
|
||||
|
||||
$alias ??= $column;
|
||||
|
||||
return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) {
|
||||
$lastId = null;
|
||||
|
||||
while (true) {
|
||||
$clone = clone $this;
|
||||
|
||||
if ($descending) {
|
||||
$results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get();
|
||||
} else {
|
||||
$results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
|
||||
}
|
||||
|
||||
foreach ($results as $result) {
|
||||
yield $result;
|
||||
}
|
||||
|
||||
if ($results->count() < $chunkSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lastId = $results->last()->{$alias};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return mixed
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|object|static|null
|
||||
*/
|
||||
public function first($columns = ['*'])
|
||||
{
|
||||
@@ -76,41 +297,149 @@ trait BuildsQueries
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback's query changes if the given "value" is true.
|
||||
* Execute the query and get the first result if it's the sole matching record.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param callable $callback
|
||||
* @param callable $default
|
||||
* @return mixed
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|object|static|null
|
||||
*
|
||||
* @throws \Illuminate\Database\RecordsNotFoundException
|
||||
* @throws \Illuminate\Database\MultipleRecordsFoundException
|
||||
*/
|
||||
public function when($value, $callback, $default = null)
|
||||
public function sole($columns = ['*'])
|
||||
{
|
||||
if ($value) {
|
||||
return $callback($this, $value) ?: $this;
|
||||
} elseif ($default) {
|
||||
return $default($this, $value) ?: $this;
|
||||
$result = $this->take(2)->get($columns);
|
||||
|
||||
$count = $result->count();
|
||||
|
||||
if ($count === 0) {
|
||||
throw new RecordsNotFoundException;
|
||||
}
|
||||
|
||||
return $this;
|
||||
if ($count > 1) {
|
||||
throw new MultipleRecordsFoundException($count);
|
||||
}
|
||||
|
||||
return $result->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback's query changes if the given "value" is false.
|
||||
* Paginate the given query using a cursor paginator.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param callable $callback
|
||||
* @param callable $default
|
||||
* @return mixed
|
||||
* @param int $perPage
|
||||
* @param array|string $columns
|
||||
* @param string $cursorName
|
||||
* @param \Illuminate\Pagination\Cursor|string|null $cursor
|
||||
* @return \Illuminate\Contracts\Pagination\CursorPaginator
|
||||
*/
|
||||
public function unless($value, $callback, $default = null)
|
||||
protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
|
||||
{
|
||||
if (! $value) {
|
||||
return $callback($this, $value) ?: $this;
|
||||
} elseif ($default) {
|
||||
return $default($this, $value) ?: $this;
|
||||
if (! $cursor instanceof Cursor) {
|
||||
$cursor = is_string($cursor)
|
||||
? Cursor::fromEncoded($cursor)
|
||||
: CursorPaginator::resolveCurrentCursor($cursorName, $cursor);
|
||||
}
|
||||
|
||||
return $this;
|
||||
$orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems());
|
||||
|
||||
if (! is_null($cursor)) {
|
||||
$addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
|
||||
$unionBuilders = isset($builder->unions) ? collect($builder->unions)->pluck('query') : collect();
|
||||
|
||||
if (! is_null($previousColumn)) {
|
||||
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn);
|
||||
|
||||
$builder->where(
|
||||
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
|
||||
'=',
|
||||
$cursor->parameter($previousColumn)
|
||||
);
|
||||
|
||||
$unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) {
|
||||
$unionBuilder->where(
|
||||
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
|
||||
'=',
|
||||
$cursor->parameter($previousColumn)
|
||||
);
|
||||
|
||||
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
|
||||
});
|
||||
}
|
||||
|
||||
$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
|
||||
['column' => $column, 'direction' => $direction] = $orders[$i];
|
||||
|
||||
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column);
|
||||
|
||||
$builder->where(
|
||||
Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
|
||||
$direction === 'asc' ? '>' : '<',
|
||||
$cursor->parameter($column)
|
||||
);
|
||||
|
||||
if ($i < $orders->count() - 1) {
|
||||
$builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
|
||||
$addCursorConditions($builder, $column, $i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
$unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
|
||||
$unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
|
||||
$unionBuilder->where(
|
||||
$this->getOriginalColumnNameForCursorPagination($this, $column),
|
||||
$direction === 'asc' ? '>' : '<',
|
||||
$cursor->parameter($column)
|
||||
);
|
||||
|
||||
if ($i < $orders->count() - 1) {
|
||||
$unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
|
||||
$addCursorConditions($builder, $column, $i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$addCursorConditions($this, null, 0);
|
||||
}
|
||||
|
||||
$this->limit($perPage + 1);
|
||||
|
||||
return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'cursorName' => $cursorName,
|
||||
'parameters' => $orders->pluck('column')->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original column name of the given column, without any aliasing.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
|
||||
* @param string $parameter
|
||||
* @return string
|
||||
*/
|
||||
protected function getOriginalColumnNameForCursorPagination($builder, string $parameter)
|
||||
{
|
||||
$columns = $builder instanceof Builder ? $builder->getQuery()->columns : $builder->columns;
|
||||
|
||||
if (! is_null($columns)) {
|
||||
foreach ($columns as $column) {
|
||||
if (($position = strripos($column, ' as ')) !== false) {
|
||||
$original = substr($column, 0, $position);
|
||||
|
||||
$alias = substr($column, $position + 4);
|
||||
|
||||
if ($parameter === $alias || $builder->getGrammar()->wrap($parameter) === $alias) {
|
||||
return $original;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,8 +463,8 @@ trait BuildsQueries
|
||||
* Create a new simple paginator instance.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param int $perPage
|
||||
* @param int $currentPage
|
||||
* @param int $perPage
|
||||
* @param int $currentPage
|
||||
* @param array $options
|
||||
* @return \Illuminate\Pagination\Paginator
|
||||
*/
|
||||
@@ -145,4 +474,33 @@ trait BuildsQueries
|
||||
'items', 'perPage', 'currentPage', 'options'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new cursor paginator instance.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param int $perPage
|
||||
* @param \Illuminate\Pagination\Cursor $cursor
|
||||
* @param array $options
|
||||
* @return \Illuminate\Pagination\CursorPaginator
|
||||
*/
|
||||
protected function cursorPaginator($items, $perPage, $cursor, $options)
|
||||
{
|
||||
return Container::getInstance()->makeWith(CursorPaginator::class, compact(
|
||||
'items', 'perPage', 'cursor', 'options'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass the query to a given callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function tap($callback)
|
||||
{
|
||||
$callback($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
64
vendor/laravel/framework/src/Illuminate/Database/Concerns/CompilesJsonPaths.php
vendored
Normal file
64
vendor/laravel/framework/src/Illuminate/Database/Concerns/CompilesJsonPaths.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait CompilesJsonPaths
|
||||
{
|
||||
/**
|
||||
* Split the given JSON selector into the field and the optional path and wrap them separately.
|
||||
*
|
||||
* @param string $column
|
||||
* @return array
|
||||
*/
|
||||
protected function wrapJsonFieldAndPath($column)
|
||||
{
|
||||
$parts = explode('->', $column, 2);
|
||||
|
||||
$field = $this->wrap($parts[0]);
|
||||
|
||||
$path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1], '->') : '';
|
||||
|
||||
return [$field, $path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given JSON path.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
protected function wrapJsonPath($value, $delimiter = '->')
|
||||
{
|
||||
$value = preg_replace("/([\\\\]+)?\\'/", "''", $value);
|
||||
|
||||
$jsonPath = collect(explode($delimiter, $value))
|
||||
->map(fn ($segment) => $this->wrapJsonPathSegment($segment))
|
||||
->join('.');
|
||||
|
||||
return "'$".(str_starts_with($jsonPath, '[') ? '' : '.').$jsonPath."'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given JSON path segment.
|
||||
*
|
||||
* @param string $segment
|
||||
* @return string
|
||||
*/
|
||||
protected function wrapJsonPathSegment($segment)
|
||||
{
|
||||
if (preg_match('/(\[[^\]]+\])+$/', $segment, $parts)) {
|
||||
$key = Str::beforeLast($segment, $parts[0]);
|
||||
|
||||
if (! empty($key)) {
|
||||
return '"'.$key.'"'.$parts[0];
|
||||
}
|
||||
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
return '"'.$segment.'"';
|
||||
}
|
||||
}
|
||||
24
vendor/laravel/framework/src/Illuminate/Database/Concerns/ExplainsQueries.php
vendored
Normal file
24
vendor/laravel/framework/src/Illuminate/Database/Concerns/ExplainsQueries.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait ExplainsQueries
|
||||
{
|
||||
/**
|
||||
* Explains the query.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function explain()
|
||||
{
|
||||
$sql = $this->toSql();
|
||||
|
||||
$bindings = $this->getBindings();
|
||||
|
||||
$explanation = $this->getConnection()->select('EXPLAIN '.$sql, $bindings);
|
||||
|
||||
return new Collection($explanation);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Database\DeadlockException;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
trait ManagesTransactions
|
||||
@@ -15,7 +16,7 @@ trait ManagesTransactions
|
||||
* @param int $attempts
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Exception|\Throwable
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function transaction(Closure $callback, $attempts = 1)
|
||||
{
|
||||
@@ -26,46 +27,69 @@ trait ManagesTransactions
|
||||
// catch any exception we can rollback this transaction so that none of this
|
||||
// gets actually persisted to a database or stored in a permanent fashion.
|
||||
try {
|
||||
return tap($callback($this), function ($result) {
|
||||
$this->commit();
|
||||
});
|
||||
$callbackResult = $callback($this);
|
||||
}
|
||||
|
||||
// If we catch an exception we'll rollback this transaction and try again if we
|
||||
// are not out of attempts. If we are out of attempts we will just throw the
|
||||
// exception back out and let the developer handle an uncaught exceptions.
|
||||
catch (Exception $e) {
|
||||
// exception back out, and let the developer handle an uncaught exception.
|
||||
catch (Throwable $e) {
|
||||
$this->handleTransactionException(
|
||||
$e, $currentAttempt, $attempts
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
$this->rollBack();
|
||||
|
||||
throw $e;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->transactions == 1) {
|
||||
$this->fireConnectionEvent('committing');
|
||||
$this->getPdo()->commit();
|
||||
}
|
||||
|
||||
$this->transactions = max(0, $this->transactions - 1);
|
||||
|
||||
if ($this->afterCommitCallbacksShouldBeExecuted()) {
|
||||
$this->transactionsManager?->commit($this->getName());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->handleCommitTransactionException(
|
||||
$e, $currentAttempt, $attempts
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->fireConnectionEvent('committed');
|
||||
|
||||
return $callbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception encountered when running a transacted statement.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @param int $currentAttempt
|
||||
* @param int $maxAttempts
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function handleTransactionException($e, $currentAttempt, $maxAttempts)
|
||||
protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
|
||||
{
|
||||
// On a deadlock, MySQL rolls back the entire transaction so we can't just
|
||||
// retry the query. We have to throw this exception all the way out and
|
||||
// let the developer handle it in another way. We will decrement too.
|
||||
if ($this->causedByDeadlock($e) &&
|
||||
if ($this->causedByConcurrencyError($e) &&
|
||||
$this->transactions > 1) {
|
||||
--$this->transactions;
|
||||
$this->transactions--;
|
||||
|
||||
throw $e;
|
||||
$this->transactionsManager?->rollback(
|
||||
$this->getName(), $this->transactions
|
||||
);
|
||||
|
||||
throw new DeadlockException($e->getMessage(), is_int($e->getCode()) ? $e->getCode() : 0, $e);
|
||||
}
|
||||
|
||||
// If there was an exception we will rollback this transaction and then we
|
||||
@@ -73,7 +97,7 @@ trait ManagesTransactions
|
||||
// if we haven't we will return and try this query again in our loop.
|
||||
$this->rollBack();
|
||||
|
||||
if ($this->causedByDeadlock($e) &&
|
||||
if ($this->causedByConcurrencyError($e) &&
|
||||
$currentAttempt < $maxAttempts) {
|
||||
return;
|
||||
}
|
||||
@@ -85,13 +109,18 @@ trait ManagesTransactions
|
||||
* Start a new database transaction.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function beginTransaction()
|
||||
{
|
||||
$this->createTransaction();
|
||||
|
||||
++$this->transactions;
|
||||
$this->transactions++;
|
||||
|
||||
$this->transactionsManager?->begin(
|
||||
$this->getName(), $this->transactions
|
||||
);
|
||||
|
||||
$this->fireConnectionEvent('beganTransaction');
|
||||
}
|
||||
@@ -100,13 +129,17 @@ trait ManagesTransactions
|
||||
* Create a transaction within the database.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function createTransaction()
|
||||
{
|
||||
if ($this->transactions == 0) {
|
||||
$this->reconnectIfMissingConnection();
|
||||
|
||||
try {
|
||||
$this->getPdo()->beginTransaction();
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->handleBeginTransactionException($e);
|
||||
}
|
||||
} elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
|
||||
@@ -118,6 +151,8 @@ trait ManagesTransactions
|
||||
* Create a save point within the database.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function createSavepoint()
|
||||
{
|
||||
@@ -129,17 +164,17 @@ trait ManagesTransactions
|
||||
/**
|
||||
* Handle an exception from a transaction beginning.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function handleBeginTransactionException($e)
|
||||
protected function handleBeginTransactionException(Throwable $e)
|
||||
{
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
$this->reconnect();
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
$this->getPdo()->beginTransaction();
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
@@ -149,23 +184,69 @@ trait ManagesTransactions
|
||||
* Commit the active database transaction.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
if ($this->transactions == 1) {
|
||||
if ($this->transactionLevel() == 1) {
|
||||
$this->fireConnectionEvent('committing');
|
||||
$this->getPdo()->commit();
|
||||
}
|
||||
|
||||
$this->transactions = max(0, $this->transactions - 1);
|
||||
|
||||
if ($this->afterCommitCallbacksShouldBeExecuted()) {
|
||||
$this->transactionsManager?->commit($this->getName());
|
||||
}
|
||||
|
||||
$this->fireConnectionEvent('committed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if after commit callbacks should be executed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function afterCommitCallbacksShouldBeExecuted()
|
||||
{
|
||||
return $this->transactions == 0 ||
|
||||
($this->transactionsManager &&
|
||||
$this->transactionsManager->callbackApplicableTransactions()->count() === 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception encountered when committing a transaction.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param int $currentAttempt
|
||||
* @param int $maxAttempts
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
|
||||
{
|
||||
$this->transactions = max(0, $this->transactions - 1);
|
||||
|
||||
if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
$this->transactions = 0;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the active database transaction.
|
||||
*
|
||||
* @param int|null $toLevel
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function rollBack($toLevel = null)
|
||||
{
|
||||
@@ -183,10 +264,18 @@ trait ManagesTransactions
|
||||
// Next, we will actually perform this rollback within this database and fire the
|
||||
// rollback event. We will also set the current transaction level to the given
|
||||
// level that was passed into this method so it will be right from here out.
|
||||
$this->performRollBack($toLevel);
|
||||
try {
|
||||
$this->performRollBack($toLevel);
|
||||
} catch (Throwable $e) {
|
||||
$this->handleRollBackException($e);
|
||||
}
|
||||
|
||||
$this->transactions = $toLevel;
|
||||
|
||||
$this->transactionsManager?->rollback(
|
||||
$this->getName(), $this->transactions
|
||||
);
|
||||
|
||||
$this->fireConnectionEvent('rollingBack');
|
||||
}
|
||||
|
||||
@@ -195,6 +284,8 @@ trait ManagesTransactions
|
||||
*
|
||||
* @param int $toLevel
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function performRollBack($toLevel)
|
||||
{
|
||||
@@ -207,6 +298,27 @@ trait ManagesTransactions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception from a rollback.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function handleRollBackException(Throwable $e)
|
||||
{
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
$this->transactions = 0;
|
||||
|
||||
$this->transactionsManager?->rollback(
|
||||
$this->getName(), $this->transactions
|
||||
);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of active transactions.
|
||||
*
|
||||
@@ -216,4 +328,21 @@ trait ManagesTransactions
|
||||
{
|
||||
return $this->transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback after a transaction commits.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function afterCommit($callback)
|
||||
{
|
||||
if ($this->transactionsManager) {
|
||||
return $this->transactionsManager->addCallback($callback);
|
||||
}
|
||||
|
||||
throw new RuntimeException('Transactions Manager has not been set.');
|
||||
}
|
||||
}
|
||||
|
||||
25
vendor/laravel/framework/src/Illuminate/Database/Concerns/ParsesSearchPath.php
vendored
Normal file
25
vendor/laravel/framework/src/Illuminate/Database/Concerns/ParsesSearchPath.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
trait ParsesSearchPath
|
||||
{
|
||||
/**
|
||||
* Parse the Postgres "search_path" configuration value into an array.
|
||||
*
|
||||
* @param string|array|null $searchPath
|
||||
* @return array
|
||||
*/
|
||||
protected function parseSearchPath($searchPath)
|
||||
{
|
||||
if (is_string($searchPath)) {
|
||||
preg_match_all('/[^\s,"\']+/', $searchPath, $matches);
|
||||
|
||||
$searchPath = $matches[0];
|
||||
}
|
||||
|
||||
return array_map(function ($schema) {
|
||||
return trim($schema, '\'"');
|
||||
}, $searchPath ?? []);
|
||||
}
|
||||
}
|
||||
10
vendor/laravel/framework/src/Illuminate/Database/ConfigurationUrlParser.php
vendored
Normal file
10
vendor/laravel/framework/src/Illuminate/Database/ConfigurationUrlParser.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Illuminate\Support\ConfigurationUrlParser as BaseConfigurationUrlParser;
|
||||
|
||||
class ConfigurationUrlParser extends BaseConfigurationUrlParser
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -2,27 +2,38 @@
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use PDO;
|
||||
use Carbon\CarbonInterval;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use PDOStatement;
|
||||
use LogicException;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Doctrine\DBAL\Connection as DoctrineConnection;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
use Doctrine\DBAL\Connection as DoctrineConnection;
|
||||
use Illuminate\Database\Query\Processors\Processor;
|
||||
use Illuminate\Database\Events\StatementPrepared;
|
||||
use Illuminate\Database\Events\TransactionBeginning;
|
||||
use Illuminate\Database\Events\TransactionCommitted;
|
||||
use Illuminate\Database\Events\TransactionCommitting;
|
||||
use Illuminate\Database\Events\TransactionRolledBack;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\Schema\Builder as SchemaBuilder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Database\Query\Grammars\Grammar as QueryGrammar;
|
||||
use Illuminate\Database\Query\Processors\Processor;
|
||||
use Illuminate\Database\Schema\Builder as SchemaBuilder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\InteractsWithTime;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use RuntimeException;
|
||||
|
||||
class Connection implements ConnectionInterface
|
||||
{
|
||||
use DetectsDeadlocks,
|
||||
use DetectsConcurrencyErrors,
|
||||
DetectsLostConnections,
|
||||
Concerns\ManagesTransactions;
|
||||
Concerns\ManagesTransactions,
|
||||
InteractsWithTime,
|
||||
Macroable;
|
||||
|
||||
/**
|
||||
* The active PDO connection.
|
||||
@@ -45,6 +56,13 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The type of the connection.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $readWriteType;
|
||||
|
||||
/**
|
||||
* The table prefix for the connection.
|
||||
*
|
||||
@@ -108,6 +126,27 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected $transactions = 0;
|
||||
|
||||
/**
|
||||
* The transaction manager instance.
|
||||
*
|
||||
* @var \Illuminate\Database\DatabaseTransactionsManager
|
||||
*/
|
||||
protected $transactionsManager;
|
||||
|
||||
/**
|
||||
* Indicates if changes have been made to the database.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $recordsModified = false;
|
||||
|
||||
/**
|
||||
* Indicates if the connection should use the "write" PDO connection.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $readOnWriteConnection = false;
|
||||
|
||||
/**
|
||||
* All of the queries run against the connection.
|
||||
*
|
||||
@@ -122,6 +161,20 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected $loggingQueries = false;
|
||||
|
||||
/**
|
||||
* The duration of all executed queries in milliseconds.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $totalQueryDuration = 0.0;
|
||||
|
||||
/**
|
||||
* All of the registered query duration handlers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queryDurationHandlers = [];
|
||||
|
||||
/**
|
||||
* Indicates if the connection is in a "dry run".
|
||||
*
|
||||
@@ -129,6 +182,13 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected $pretending = false;
|
||||
|
||||
/**
|
||||
* All of the callbacks that should be invoked before a query is executed.
|
||||
*
|
||||
* @var \Closure[]
|
||||
*/
|
||||
protected $beforeExecutingCallbacks = [];
|
||||
|
||||
/**
|
||||
* The instance of Doctrine connection.
|
||||
*
|
||||
@@ -136,20 +196,27 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected $doctrineConnection;
|
||||
|
||||
/**
|
||||
* Type mappings that should be registered with new Doctrine connections.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $doctrineTypeMappings = [];
|
||||
|
||||
/**
|
||||
* The connection resolvers.
|
||||
*
|
||||
* @var array
|
||||
* @var \Closure[]
|
||||
*/
|
||||
protected static $resolvers = [];
|
||||
|
||||
/**
|
||||
* Create a new database connection instance.
|
||||
*
|
||||
* @param \PDO|\Closure $pdo
|
||||
* @param string $database
|
||||
* @param string $tablePrefix
|
||||
* @param array $config
|
||||
* @param \PDO|\Closure $pdo
|
||||
* @param string $database
|
||||
* @param string $tablePrefix
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
|
||||
@@ -250,12 +317,13 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Begin a fluent query against a database table.
|
||||
*
|
||||
* @param string $table
|
||||
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
|
||||
* @param string|null $as
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function table($table)
|
||||
public function table($table, $as = null)
|
||||
{
|
||||
return $this->query()->from($table);
|
||||
return $this->query()->from($table, $as);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +342,7 @@ class Connection implements ConnectionInterface
|
||||
* Run a select statement and return a single result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -285,11 +353,38 @@ class Connection implements ConnectionInterface
|
||||
return array_shift($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a select statement and return the first column of the first row.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Database\MultipleColumnsSelectedException
|
||||
*/
|
||||
public function scalar($query, $bindings = [], $useReadPdo = true)
|
||||
{
|
||||
$record = $this->selectOne($query, $bindings, $useReadPdo);
|
||||
|
||||
if (is_null($record)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$record = (array) $record;
|
||||
|
||||
if (count($record) > 1) {
|
||||
throw new MultipleColumnsSelectedException;
|
||||
}
|
||||
|
||||
return reset($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a select statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return array
|
||||
*/
|
||||
public function selectFromWriteConnection($query, $bindings = [])
|
||||
@@ -315,8 +410,9 @@ class Connection implements ConnectionInterface
|
||||
// For select statements, we'll simply execute the query and return an array
|
||||
// of the database result set. Each element in the array will be a single
|
||||
// row from the database table, and will either be an array or objects.
|
||||
$statement = $this->prepared($this->getPdoForSelect($useReadPdo)
|
||||
->prepare($query));
|
||||
$statement = $this->prepared(
|
||||
$this->getPdoForSelect($useReadPdo)->prepare($query)
|
||||
);
|
||||
|
||||
$this->bindValues($statement, $this->prepareBindings($bindings));
|
||||
|
||||
@@ -374,9 +470,7 @@ class Connection implements ConnectionInterface
|
||||
{
|
||||
$statement->setFetchMode($this->fetchMode);
|
||||
|
||||
$this->event(new Events\StatementPrepared(
|
||||
$this, $statement
|
||||
));
|
||||
$this->event(new StatementPrepared($this, $statement));
|
||||
|
||||
return $statement;
|
||||
}
|
||||
@@ -396,7 +490,7 @@ class Connection implements ConnectionInterface
|
||||
* Run an insert statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function insert($query, $bindings = [])
|
||||
@@ -408,7 +502,7 @@ class Connection implements ConnectionInterface
|
||||
* Run an update statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function update($query, $bindings = [])
|
||||
@@ -420,7 +514,7 @@ class Connection implements ConnectionInterface
|
||||
* Run a delete statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function delete($query, $bindings = [])
|
||||
@@ -432,7 +526,7 @@ class Connection implements ConnectionInterface
|
||||
* Execute an SQL statement and return the boolean result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function statement($query, $bindings = [])
|
||||
@@ -446,6 +540,8 @@ class Connection implements ConnectionInterface
|
||||
|
||||
$this->bindValues($statement, $this->prepareBindings($bindings));
|
||||
|
||||
$this->recordsHaveBeenModified();
|
||||
|
||||
return $statement->execute();
|
||||
});
|
||||
}
|
||||
@@ -454,7 +550,7 @@ class Connection implements ConnectionInterface
|
||||
* Run an SQL statement and get the number of rows affected.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function affectingStatement($query, $bindings = [])
|
||||
@@ -473,7 +569,11 @@ class Connection implements ConnectionInterface
|
||||
|
||||
$statement->execute();
|
||||
|
||||
return $statement->rowCount();
|
||||
$this->recordsHaveBeenModified(
|
||||
($count = $statement->rowCount()) > 0
|
||||
);
|
||||
|
||||
return $count;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -490,7 +590,11 @@ class Connection implements ConnectionInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->getPdo()->exec($query) === false ? false : true;
|
||||
$this->recordsHaveBeenModified(
|
||||
$change = $this->getPdo()->exec($query) !== false
|
||||
);
|
||||
|
||||
return $change;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -535,7 +639,7 @@ class Connection implements ConnectionInterface
|
||||
|
||||
// Now we'll execute this callback and capture the result. Once it has been
|
||||
// executed we will restore the value of query logging and give back the
|
||||
// value of hte callback so the original callers can have the results.
|
||||
// value of the callback so the original callers can have the results.
|
||||
$result = $callback();
|
||||
|
||||
$this->loggingQueries = $loggingQueries;
|
||||
@@ -546,7 +650,7 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Bind values to their parameters in the given statement.
|
||||
*
|
||||
* @param \PDOStatement $statement
|
||||
* @param \PDOStatement $statement
|
||||
* @param array $bindings
|
||||
* @return void
|
||||
*/
|
||||
@@ -554,8 +658,13 @@ class Connection implements ConnectionInterface
|
||||
{
|
||||
foreach ($bindings as $key => $value) {
|
||||
$statement->bindValue(
|
||||
is_string($key) ? $key : $key + 1, $value,
|
||||
is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
|
||||
is_string($key) ? $key : $key + 1,
|
||||
$value,
|
||||
match (true) {
|
||||
is_int($value) => PDO::PARAM_INT,
|
||||
is_resource($value) => PDO::PARAM_LOB,
|
||||
default => PDO::PARAM_STR
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -576,8 +685,8 @@ class Connection implements ConnectionInterface
|
||||
// so we'll just ask the grammar for the format to get from the date.
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
$bindings[$key] = $value->format($grammar->getDateFormat());
|
||||
} elseif ($value === false) {
|
||||
$bindings[$key] = 0;
|
||||
} elseif (is_bool($value)) {
|
||||
$bindings[$key] = (int) $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,8 +696,8 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Run a SQL statement and log its execution context.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*
|
||||
@@ -596,6 +705,10 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected function run($query, $bindings, Closure $callback)
|
||||
{
|
||||
foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
|
||||
$beforeExecutingCallback($query, $bindings, $this);
|
||||
}
|
||||
|
||||
$this->reconnectIfMissingConnection();
|
||||
|
||||
$start = microtime(true);
|
||||
@@ -624,8 +737,8 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Run a SQL statement.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*
|
||||
@@ -637,7 +750,7 @@ class Connection implements ConnectionInterface
|
||||
// run the SQL against the PDO connection. Then we can calculate the time it
|
||||
// took to execute and log the query SQL, bindings and time in our memory.
|
||||
try {
|
||||
$result = $callback($query, $bindings);
|
||||
return $callback($query, $bindings);
|
||||
}
|
||||
|
||||
// If an exception occurs when attempting to run a query, we'll format the error
|
||||
@@ -648,20 +761,20 @@ class Connection implements ConnectionInterface
|
||||
$query, $this->prepareBindings($bindings), $e
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a query in the connection's query log.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @param float|null $time
|
||||
* @return void
|
||||
*/
|
||||
public function logQuery($query, $bindings, $time = null)
|
||||
{
|
||||
$this->totalQueryDuration += $time ?? 0.0;
|
||||
|
||||
$this->event(new QueryExecuted($query, $bindings, $time, $this));
|
||||
|
||||
if ($this->loggingQueries) {
|
||||
@@ -672,7 +785,7 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Get the elapsed time since a given starting point.
|
||||
*
|
||||
* @param int $start
|
||||
* @param int $start
|
||||
* @return float
|
||||
*/
|
||||
protected function getElapsedTime($start)
|
||||
@@ -680,17 +793,83 @@ class Connection implements ConnectionInterface
|
||||
return round((microtime(true) - $start) * 1000, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when the connection queries for longer than a given amount of time.
|
||||
*
|
||||
* @param \DateTimeInterface|\Carbon\CarbonInterval|float|int $threshold
|
||||
* @param callable $handler
|
||||
* @return void
|
||||
*/
|
||||
public function whenQueryingForLongerThan($threshold, $handler)
|
||||
{
|
||||
$threshold = $threshold instanceof DateTimeInterface
|
||||
? $this->secondsUntil($threshold) * 1000
|
||||
: $threshold;
|
||||
|
||||
$threshold = $threshold instanceof CarbonInterval
|
||||
? $threshold->totalMilliseconds
|
||||
: $threshold;
|
||||
|
||||
$this->queryDurationHandlers[] = [
|
||||
'has_run' => false,
|
||||
'handler' => $handler,
|
||||
];
|
||||
|
||||
$key = count($this->queryDurationHandlers) - 1;
|
||||
|
||||
$this->listen(function ($event) use ($threshold, $handler, $key) {
|
||||
if (! $this->queryDurationHandlers[$key]['has_run'] && $this->totalQueryDuration() > $threshold) {
|
||||
$handler($this, $event);
|
||||
|
||||
$this->queryDurationHandlers[$key]['has_run'] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow all the query duration handlers to run again, even if they have already run.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function allowQueryDurationHandlersToRunAgain()
|
||||
{
|
||||
foreach ($this->queryDurationHandlers as $key => $queryDurationHandler) {
|
||||
$this->queryDurationHandlers[$key]['has_run'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration of all run queries in milliseconds.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function totalQueryDuration()
|
||||
{
|
||||
return $this->totalQueryDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the duration of all run queries.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetTotalQueryDuration()
|
||||
{
|
||||
$this->totalQueryDuration = 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a query exception.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Illuminate\Database\QueryException $e
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*
|
||||
* @throws \Illuminate\Database\QueryException
|
||||
*/
|
||||
protected function handleQueryException($e, $query, $bindings, Closure $callback)
|
||||
protected function handleQueryException(QueryException $e, $query, $bindings, Closure $callback)
|
||||
{
|
||||
if ($this->transactions >= 1) {
|
||||
throw $e;
|
||||
@@ -705,8 +884,8 @@ class Connection implements ConnectionInterface
|
||||
* Handle a query exception that occurred during query execution.
|
||||
*
|
||||
* @param \Illuminate\Database\QueryException $e
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*
|
||||
@@ -726,17 +905,19 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Reconnect to the database.
|
||||
*
|
||||
* @return void
|
||||
* @return mixed|false
|
||||
*
|
||||
* @throws \LogicException
|
||||
* @throws \Illuminate\Database\LostConnectionException
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
if (is_callable($this->reconnector)) {
|
||||
$this->doctrineConnection = null;
|
||||
|
||||
return call_user_func($this->reconnector, $this);
|
||||
}
|
||||
|
||||
throw new LogicException('Lost connection and no reconnector available.');
|
||||
throw new LostConnectionException('Lost connection and no reconnector available.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -759,6 +940,21 @@ class Connection implements ConnectionInterface
|
||||
public function disconnect()
|
||||
{
|
||||
$this->setPdo(null)->setReadPdo(null);
|
||||
|
||||
$this->doctrineConnection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a hook to be run just before a database query is executed.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function beforeExecuting(Closure $callback)
|
||||
{
|
||||
$this->beforeExecutingCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -769,9 +965,7 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
public function listen(Closure $callback)
|
||||
{
|
||||
if (isset($this->events)) {
|
||||
$this->events->listen(Events\QueryExecuted::class, $callback);
|
||||
}
|
||||
$this->events?->listen(Events\QueryExecuted::class, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -782,18 +976,13 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected function fireConnectionEvent($event)
|
||||
{
|
||||
if (! isset($this->events)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($event) {
|
||||
case 'beganTransaction':
|
||||
return $this->events->dispatch(new Events\TransactionBeginning($this));
|
||||
case 'committed':
|
||||
return $this->events->dispatch(new Events\TransactionCommitted($this));
|
||||
case 'rollingBack':
|
||||
return $this->events->dispatch(new Events\TransactionRolledBack($this));
|
||||
}
|
||||
return $this->events?->dispatch(match ($event) {
|
||||
'beganTransaction' => new TransactionBeginning($this),
|
||||
'committed' => new TransactionCommitted($this),
|
||||
'committing' => new TransactionCommitting($this),
|
||||
'rollingBack' => new TransactionRolledBack($this),
|
||||
default => null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -804,9 +993,7 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
protected function event($event)
|
||||
{
|
||||
if (isset($this->events)) {
|
||||
$this->events->dispatch($event);
|
||||
}
|
||||
$this->events?->dispatch($event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -820,6 +1007,65 @@ class Connection implements ConnectionInterface
|
||||
return new Expression($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the database connection has modified any database records.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasModifiedRecords()
|
||||
{
|
||||
return $this->recordsModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if any records have been modified.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return void
|
||||
*/
|
||||
public function recordsHaveBeenModified($value = true)
|
||||
{
|
||||
if (! $this->recordsModified) {
|
||||
$this->recordsModified = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the record modification state.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setRecordModificationState(bool $value)
|
||||
{
|
||||
$this->recordsModified = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the record modification state.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forgetRecordModificationState()
|
||||
{
|
||||
$this->recordsModified = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the connection should use the write PDO connection for reads.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function useWriteConnectionWhenReading($value = true)
|
||||
{
|
||||
$this->readOnWriteConnection = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Doctrine available?
|
||||
*
|
||||
@@ -830,6 +1076,16 @@ class Connection implements ConnectionInterface
|
||||
return class_exists('Doctrine\DBAL\Connection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether native alter operations will be used when dropping or renaming columns, even if Doctrine DBAL is installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usingNativeSchemaOperations()
|
||||
{
|
||||
return ! $this->isDoctrineAvailable() || SchemaBuilder::$alwaysUsesNativeSchemaOperationsIfPossible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Doctrine Schema Column instance.
|
||||
*
|
||||
@@ -851,7 +1107,13 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
public function getDoctrineSchemaManager()
|
||||
{
|
||||
return $this->getDoctrineDriver()->getSchemaManager($this->getDoctrineConnection());
|
||||
$connection = $this->getDoctrineConnection();
|
||||
|
||||
// Doctrine v2 expects one parameter while v3 expects two. 2nd will be ignored on v2...
|
||||
return $this->getDoctrineDriver()->getSchemaManager(
|
||||
$connection,
|
||||
$connection->getDatabasePlatform()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -862,16 +1124,52 @@ class Connection implements ConnectionInterface
|
||||
public function getDoctrineConnection()
|
||||
{
|
||||
if (is_null($this->doctrineConnection)) {
|
||||
$data = ['pdo' => $this->getPdo(), 'dbname' => $this->getConfig('database')];
|
||||
$driver = $this->getDoctrineDriver();
|
||||
|
||||
$this->doctrineConnection = new DoctrineConnection(
|
||||
$data, $this->getDoctrineDriver()
|
||||
);
|
||||
$this->doctrineConnection = new DoctrineConnection(array_filter([
|
||||
'pdo' => $this->getPdo(),
|
||||
'dbname' => $this->getDatabaseName(),
|
||||
'driver' => $driver->getName(),
|
||||
'serverVersion' => $this->getConfig('server_version'),
|
||||
]), $driver);
|
||||
|
||||
foreach ($this->doctrineTypeMappings as $name => $type) {
|
||||
$this->doctrineConnection
|
||||
->getDatabasePlatform()
|
||||
->registerDoctrineTypeMapping($type, $name);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->doctrineConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom Doctrine mapping type.
|
||||
*
|
||||
* @param Type|class-string<Type> $class
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @return void
|
||||
*
|
||||
* @throws \Doctrine\DBAL\DBALException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function registerDoctrineType(Type|string $class, string $name, string $type): void
|
||||
{
|
||||
if (! $this->isDoctrineAvailable()) {
|
||||
throw new RuntimeException(
|
||||
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
|
||||
);
|
||||
}
|
||||
|
||||
if (! Type::hasType($name)) {
|
||||
Type::getTypeRegistry()
|
||||
->register($name, is_string($class) ? new $class() : $class);
|
||||
}
|
||||
|
||||
$this->doctrineTypeMappings[$name] = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current PDO connection.
|
||||
*
|
||||
@@ -886,6 +1184,16 @@ class Connection implements ConnectionInterface
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current PDO connection parameter without executing any reconnect logic.
|
||||
*
|
||||
* @return \PDO|\Closure|null
|
||||
*/
|
||||
public function getRawPdo()
|
||||
{
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current PDO connection used for reading.
|
||||
*
|
||||
@@ -893,7 +1201,12 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
public function getReadPdo()
|
||||
{
|
||||
if ($this->transactions >= 1) {
|
||||
if ($this->transactions > 0) {
|
||||
return $this->getPdo();
|
||||
}
|
||||
|
||||
if ($this->readOnWriteConnection ||
|
||||
($this->recordsModified && $this->getConfig('sticky'))) {
|
||||
return $this->getPdo();
|
||||
}
|
||||
|
||||
@@ -904,6 +1217,16 @@ class Connection implements ConnectionInterface
|
||||
return $this->readPdo ?: $this->getPdo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current read PDO connection parameter without executing any reconnect logic.
|
||||
*
|
||||
* @return \PDO|\Closure|null
|
||||
*/
|
||||
public function getRawReadPdo()
|
||||
{
|
||||
return $this->readPdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PDO connection.
|
||||
*
|
||||
@@ -922,7 +1245,7 @@ class Connection implements ConnectionInterface
|
||||
/**
|
||||
* Set the PDO connection used for reading.
|
||||
*
|
||||
* @param \PDO||\Closure|null $pdo
|
||||
* @param \PDO|\Closure|null $pdo
|
||||
* @return $this
|
||||
*/
|
||||
public function setReadPdo($pdo)
|
||||
@@ -955,6 +1278,16 @@ class Connection implements ConnectionInterface
|
||||
return $this->getConfig('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection full name.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNameWithReadWriteType()
|
||||
{
|
||||
return $this->getName().($this->readWriteType ? '::'.$this->readWriteType : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option from the configuration options.
|
||||
*
|
||||
@@ -990,11 +1323,13 @@ class Connection implements ConnectionInterface
|
||||
* Set the query grammar used by the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Grammars\Grammar $grammar
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
public function setQueryGrammar(Query\Grammars\Grammar $grammar)
|
||||
{
|
||||
$this->queryGrammar = $grammar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1011,11 +1346,13 @@ class Connection implements ConnectionInterface
|
||||
* Set the schema grammar used by the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
public function setSchemaGrammar(Schema\Grammars\Grammar $grammar)
|
||||
{
|
||||
$this->schemaGrammar = $grammar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1032,11 +1369,13 @@ class Connection implements ConnectionInterface
|
||||
* Set the query post processor used by the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Processors\Processor $processor
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
public function setPostProcessor(Processor $processor)
|
||||
{
|
||||
$this->postProcessor = $processor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1053,15 +1392,50 @@ class Connection implements ConnectionInterface
|
||||
* Set the event dispatcher instance on the connection.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
public function setEventDispatcher(Dispatcher $events)
|
||||
{
|
||||
$this->events = $events;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the connection in a "dry run".
|
||||
* Unset the event dispatcher for this connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetEventDispatcher()
|
||||
{
|
||||
$this->events = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction manager instance on the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\DatabaseTransactionsManager $manager
|
||||
* @return $this
|
||||
*/
|
||||
public function setTransactionManager($manager)
|
||||
{
|
||||
$this->transactionsManager = $manager;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the transaction manager for this connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetTransactionManager()
|
||||
{
|
||||
$this->transactionsManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the connection is in a "dry run".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -1134,11 +1508,26 @@ class Connection implements ConnectionInterface
|
||||
* Set the name of the connected database.
|
||||
*
|
||||
* @param string $database
|
||||
* @return string
|
||||
* @return $this
|
||||
*/
|
||||
public function setDatabaseName($database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read / write type of the connection.
|
||||
*
|
||||
* @param string|null $readWriteType
|
||||
* @return $this
|
||||
*/
|
||||
public function setReadWriteType($readWriteType)
|
||||
{
|
||||
$this->readWriteType = $readWriteType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1155,13 +1544,15 @@ class Connection implements ConnectionInterface
|
||||
* Set the table prefix in use by the connection.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
public function setTablePrefix($prefix)
|
||||
{
|
||||
$this->tablePrefix = $prefix;
|
||||
|
||||
$this->getQueryGrammar()->setTablePrefix($prefix);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1197,7 +1588,6 @@ class Connection implements ConnectionInterface
|
||||
*/
|
||||
public static function getResolver($driver)
|
||||
{
|
||||
return isset(static::$resolvers[$driver]) ?
|
||||
static::$resolvers[$driver] : null;
|
||||
return static::$resolvers[$driver] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ interface ConnectionInterface
|
||||
/**
|
||||
* Begin a fluent query against a database table.
|
||||
*
|
||||
* @param string $table
|
||||
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
|
||||
* @param string|null $as
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function table($table);
|
||||
public function table($table, $as = null);
|
||||
|
||||
/**
|
||||
* Get a new raw query expression.
|
||||
@@ -26,25 +27,37 @@ interface ConnectionInterface
|
||||
* Run a select statement and return a single result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return mixed
|
||||
*/
|
||||
public function selectOne($query, $bindings = []);
|
||||
public function selectOne($query, $bindings = [], $useReadPdo = true);
|
||||
|
||||
/**
|
||||
* Run a select statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return array
|
||||
*/
|
||||
public function select($query, $bindings = []);
|
||||
public function select($query, $bindings = [], $useReadPdo = true);
|
||||
|
||||
/**
|
||||
* Run a select statement against the database and returns a generator.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return \Generator
|
||||
*/
|
||||
public function cursor($query, $bindings = [], $useReadPdo = true);
|
||||
|
||||
/**
|
||||
* Run an insert statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function insert($query, $bindings = []);
|
||||
@@ -53,7 +66,7 @@ interface ConnectionInterface
|
||||
* Run an update statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function update($query, $bindings = []);
|
||||
@@ -62,7 +75,7 @@ interface ConnectionInterface
|
||||
* Run a delete statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function delete($query, $bindings = []);
|
||||
@@ -71,7 +84,7 @@ interface ConnectionInterface
|
||||
* Execute an SQL statement and return the boolean result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function statement($query, $bindings = []);
|
||||
@@ -80,7 +93,7 @@ interface ConnectionInterface
|
||||
* Run an SQL statement and get the number of rows affected.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function affectingStatement($query, $bindings = []);
|
||||
@@ -147,4 +160,11 @@ interface ConnectionInterface
|
||||
* @return array
|
||||
*/
|
||||
public function pretend(Closure $callback);
|
||||
|
||||
/**
|
||||
* Get the name of the connected database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatabaseName();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ class ConnectionResolver implements ConnectionResolverInterface
|
||||
/**
|
||||
* All of the registered connections.
|
||||
*
|
||||
* @var array
|
||||
* @var \Illuminate\Database\ConnectionInterface[]
|
||||
*/
|
||||
protected $connections = [];
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConnectionResolver implements ConnectionResolverInterface
|
||||
/**
|
||||
* Create a new connection resolver instance.
|
||||
*
|
||||
* @param array $connections
|
||||
* @param array<string, \Illuminate\Database\ConnectionInterface> $connections
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $connections = [])
|
||||
@@ -34,7 +34,7 @@ class ConnectionResolver implements ConnectionResolverInterface
|
||||
/**
|
||||
* Get a database connection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\ConnectionInterface
|
||||
*/
|
||||
public function connection($name = null)
|
||||
|
||||
@@ -7,7 +7,7 @@ interface ConnectionResolverInterface
|
||||
/**
|
||||
* Get a database connection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\ConnectionInterface
|
||||
*/
|
||||
public function connection($name = null);
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use PDOException;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\MySqlConnection;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use PDOException;
|
||||
|
||||
class ConnectionFactory
|
||||
{
|
||||
@@ -36,8 +35,8 @@ class ConnectionFactory
|
||||
/**
|
||||
* Establish a PDO connection based on the configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param string $name
|
||||
* @param array $config
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function make(array $config, $name = null)
|
||||
@@ -54,7 +53,7 @@ class ConnectionFactory
|
||||
/**
|
||||
* Parse and prepare the database configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $config
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
@@ -79,7 +78,7 @@ class ConnectionFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single database connection instance.
|
||||
* Create a read / write database connection instance.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Database\Connection
|
||||
@@ -116,7 +115,7 @@ class ConnectionFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the read configuration for a read / write connection.
|
||||
* Get the write configuration for a read / write connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return array
|
||||
@@ -131,7 +130,7 @@ class ConnectionFactory
|
||||
/**
|
||||
* Get a read / write level configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $config
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
@@ -172,19 +171,19 @@ class ConnectionFactory
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Closure
|
||||
*
|
||||
* @throws \PDOException
|
||||
*/
|
||||
protected function createPdoResolverWithHosts(array $config)
|
||||
{
|
||||
return function () use ($config) {
|
||||
foreach (Arr::shuffle($hosts = $this->parseHosts($config)) as $key => $host) {
|
||||
foreach (Arr::shuffle($this->parseHosts($config)) as $host) {
|
||||
$config['host'] = $host;
|
||||
|
||||
try {
|
||||
return $this->createConnector($config)->connect($config);
|
||||
} catch (PDOException $e) {
|
||||
if (count($hosts) - 1 === $key && $this->container->bound(ExceptionHandler::class)) {
|
||||
$this->container->make(ExceptionHandler::class)->report($e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,10 +196,12 @@ class ConnectionFactory
|
||||
*
|
||||
* @param array $config
|
||||
* @return array
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function parseHosts(array $config)
|
||||
{
|
||||
$hosts = array_wrap($config['host']);
|
||||
$hosts = Arr::wrap($config['host']);
|
||||
|
||||
if (empty($hosts)) {
|
||||
throw new InvalidArgumentException('Database hosts array is empty.');
|
||||
@@ -217,9 +218,7 @@ class ConnectionFactory
|
||||
*/
|
||||
protected function createPdoResolverWithoutHosts(array $config)
|
||||
{
|
||||
return function () use ($config) {
|
||||
return $this->createConnector($config)->connect($config);
|
||||
};
|
||||
return fn () => $this->createConnector($config)->connect($config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,28 +239,23 @@ class ConnectionFactory
|
||||
return $this->container->make($key);
|
||||
}
|
||||
|
||||
switch ($config['driver']) {
|
||||
case 'mysql':
|
||||
return new MySqlConnector;
|
||||
case 'pgsql':
|
||||
return new PostgresConnector;
|
||||
case 'sqlite':
|
||||
return new SQLiteConnector;
|
||||
case 'sqlsrv':
|
||||
return new SqlServerConnector;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]");
|
||||
return match ($config['driver']) {
|
||||
'mysql' => new MySqlConnector,
|
||||
'pgsql' => new PostgresConnector,
|
||||
'sqlite' => new SQLiteConnector,
|
||||
'sqlsrv' => new SqlServerConnector,
|
||||
default => throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connection instance.
|
||||
*
|
||||
* @param string $driver
|
||||
* @param \PDO|\Closure $connection
|
||||
* @param string $database
|
||||
* @param string $prefix
|
||||
* @param array $config
|
||||
* @param string $driver
|
||||
* @param \PDO|\Closure $connection
|
||||
* @param string $database
|
||||
* @param string $prefix
|
||||
* @param array $config
|
||||
* @return \Illuminate\Database\Connection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
@@ -272,17 +266,12 @@ class ConnectionFactory
|
||||
return $resolver($connection, $database, $prefix, $config);
|
||||
}
|
||||
|
||||
switch ($driver) {
|
||||
case 'mysql':
|
||||
return new MySqlConnection($connection, $database, $prefix, $config);
|
||||
case 'pgsql':
|
||||
return new PostgresConnection($connection, $database, $prefix, $config);
|
||||
case 'sqlite':
|
||||
return new SQLiteConnection($connection, $database, $prefix, $config);
|
||||
case 'sqlsrv':
|
||||
return new SqlServerConnection($connection, $database, $prefix, $config);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Unsupported driver [$driver]");
|
||||
return match ($driver) {
|
||||
'mysql' => new MySqlConnection($connection, $database, $prefix, $config),
|
||||
'pgsql' => new PostgresConnection($connection, $database, $prefix, $config),
|
||||
'sqlite' => new SQLiteConnection($connection, $database, $prefix, $config),
|
||||
'sqlsrv' => new SqlServerConnection($connection, $database, $prefix, $config),
|
||||
default => throw new InvalidArgumentException("Unsupported driver [{$driver}]."),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use PDO;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Doctrine\DBAL\Driver\PDOConnection;
|
||||
use Exception;
|
||||
use Illuminate\Database\DetectsLostConnections;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
|
||||
class Connector
|
||||
{
|
||||
@@ -29,14 +29,16 @@ class Connector
|
||||
* Create a new PDO connection.
|
||||
*
|
||||
* @param string $dsn
|
||||
* @param array $config
|
||||
* @param array $options
|
||||
* @param array $config
|
||||
* @param array $options
|
||||
* @return \PDO
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createConnection($dsn, array $config, array $options)
|
||||
{
|
||||
list($username, $password) = [
|
||||
Arr::get($config, 'username'), Arr::get($config, 'password'),
|
||||
[$username, $password] = [
|
||||
$config['username'] ?? null, $config['password'] ?? null,
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -83,16 +85,16 @@ class Connector
|
||||
/**
|
||||
* Handle an exception that occurred during connect execution.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @param string $dsn
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param array $options
|
||||
* @param array $options
|
||||
* @return \PDO
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function tryAgainIfCausedByLostConnection(Exception $e, $dsn, $username, $password, $options)
|
||||
protected function tryAgainIfCausedByLostConnection(Throwable $e, $dsn, $username, $password, $options)
|
||||
{
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
return $this->createPdoConnection($dsn, $username, $password, $options);
|
||||
@@ -109,7 +111,7 @@ class Connector
|
||||
*/
|
||||
public function getOptions(array $config)
|
||||
{
|
||||
$options = Arr::get($config, 'options', []);
|
||||
$options = $config['options'] ?? [];
|
||||
|
||||
return array_diff_key($this->options, $options) + $options;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ class MySqlConnector extends Connector implements ConnectorInterface
|
||||
$connection->exec("use `{$config['database']}`;");
|
||||
}
|
||||
|
||||
$this->configureIsolationLevel($connection, $config);
|
||||
|
||||
$this->configureEncoding($connection, $config);
|
||||
|
||||
// Next, we will check to see if a timezone has been specified in this config
|
||||
@@ -40,12 +42,30 @@ class MySqlConnector extends Connector implements ConnectorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection character set and collation.
|
||||
* Set the connection transaction isolation level.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureIsolationLevel($connection, array $config)
|
||||
{
|
||||
if (! isset($config['isolation_level'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare(
|
||||
"SET SESSION TRANSACTION ISOLATION LEVEL {$config['isolation_level']}"
|
||||
)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection character set and collation.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void|\PDO
|
||||
*/
|
||||
protected function configureEncoding($connection, array $config)
|
||||
{
|
||||
if (! isset($config['charset'])) {
|
||||
@@ -87,7 +107,7 @@ class MySqlConnector extends Connector implements ConnectorInterface
|
||||
*
|
||||
* Chooses socket or host/port based on the 'unix_socket' config value.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getDsn(array $config)
|
||||
@@ -147,7 +167,7 @@ class MySqlConnector extends Connector implements ConnectorInterface
|
||||
$this->setCustomModes($connection, $config);
|
||||
} elseif (isset($config['strict'])) {
|
||||
if ($config['strict']) {
|
||||
$connection->prepare($this->strictMode())->execute();
|
||||
$connection->prepare($this->strictMode($connection, $config))->execute();
|
||||
} else {
|
||||
$connection->prepare("set session sql_mode='NO_ENGINE_SUBSTITUTION'")->execute();
|
||||
}
|
||||
@@ -171,10 +191,18 @@ class MySqlConnector extends Connector implements ConnectorInterface
|
||||
/**
|
||||
* Get the query to enable strict mode.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function strictMode()
|
||||
protected function strictMode(PDO $connection, $config)
|
||||
{
|
||||
$version = $config['version'] ?? $connection->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
|
||||
if (version_compare($version, '8.0.11') >= 0) {
|
||||
return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
|
||||
}
|
||||
|
||||
return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use Illuminate\Database\Concerns\ParsesSearchPath;
|
||||
use PDO;
|
||||
|
||||
class PostgresConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
use ParsesSearchPath;
|
||||
|
||||
/**
|
||||
* The default PDO connection options.
|
||||
*
|
||||
@@ -33,6 +36,8 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
$this->getDsn($config), $config, $this->getOptions($config)
|
||||
);
|
||||
|
||||
$this->configureIsolationLevel($connection, $config);
|
||||
|
||||
$this->configureEncoding($connection, $config);
|
||||
|
||||
// Next, we will check to see if a timezone has been specified in this config
|
||||
@@ -40,16 +45,32 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
// database. Setting this DB timezone is an optional configuration item.
|
||||
$this->configureTimezone($connection, $config);
|
||||
|
||||
$this->configureSchema($connection, $config);
|
||||
$this->configureSearchPath($connection, $config);
|
||||
|
||||
// Postgres allows an application_name to be set by the user and this name is
|
||||
// used to when monitoring the application with pg_stat_activity. So we'll
|
||||
// determine if the option has been specified and run a statement if so.
|
||||
$this->configureApplicationName($connection, $config);
|
||||
|
||||
$this->configureSynchronousCommit($connection, $config);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection transaction isolation level.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureIsolationLevel($connection, array $config)
|
||||
{
|
||||
if (isset($config['isolation_level'])) {
|
||||
$connection->prepare("set session characteristics as transaction isolation level {$config['isolation_level']}")->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection character set and collation.
|
||||
*
|
||||
@@ -59,9 +80,11 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
*/
|
||||
protected function configureEncoding($connection, $config)
|
||||
{
|
||||
$charset = $config['charset'];
|
||||
if (! isset($config['charset'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare("set names '$charset'")->execute();
|
||||
$connection->prepare("set names '{$config['charset']}'")->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,38 +104,36 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the schema on the connection.
|
||||
* Set the "search_path" on the database connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSchema($connection, $config)
|
||||
protected function configureSearchPath($connection, $config)
|
||||
{
|
||||
if (isset($config['schema'])) {
|
||||
$schema = $this->formatSchema($config['schema']);
|
||||
if (isset($config['search_path']) || isset($config['schema'])) {
|
||||
$searchPath = $this->quoteSearchPath(
|
||||
$this->parseSearchPath($config['search_path'] ?? $config['schema'])
|
||||
);
|
||||
|
||||
$connection->prepare("set search_path to {$schema}")->execute();
|
||||
$connection->prepare("set search_path to {$searchPath}")->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the schema for the DSN.
|
||||
* Format the search path for the DSN.
|
||||
*
|
||||
* @param array|string $schema
|
||||
* @param array $searchPath
|
||||
* @return string
|
||||
*/
|
||||
protected function formatSchema($schema)
|
||||
protected function quoteSearchPath($searchPath)
|
||||
{
|
||||
if (is_array($schema)) {
|
||||
return '"'.implode('", "', $schema).'"';
|
||||
} else {
|
||||
return '"'.$schema.'"';
|
||||
}
|
||||
return count($searchPath) === 1 ? '"'.$searchPath[0].'"' : '"'.implode('", "', $searchPath).'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the schema on the connection.
|
||||
* Set the application name on the connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
@@ -130,7 +151,7 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
/**
|
||||
* Create a DSN string from a configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getDsn(array $config)
|
||||
@@ -142,7 +163,12 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
|
||||
$host = isset($host) ? "host={$host};" : '';
|
||||
|
||||
$dsn = "pgsql:{$host}dbname={$database}";
|
||||
// Sometimes - users may need to connect to a database that has a different
|
||||
// name than the database used for "information_schema" queries. This is
|
||||
// typically the case if using "pgbouncer" type software when pooling.
|
||||
$database = $connect_via_database ?? $database;
|
||||
|
||||
$dsn = "pgsql:{$host}dbname='{$database}'";
|
||||
|
||||
// If a port was specified, we will add it to this Postgres DSN connections
|
||||
// format. Once we have done that we are ready to return this connection
|
||||
@@ -171,4 +197,20 @@ class PostgresConnector extends Connector implements ConnectorInterface
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the synchronous_commit setting.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSynchronousCommit($connection, array $config)
|
||||
{
|
||||
if (! isset($config['synchronous_commit'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare("set synchronous_commit to '{$config['synchronous_commit']}'")->execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Database\SQLiteDatabaseDoesNotExistException;
|
||||
|
||||
class SQLiteConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
@@ -12,7 +12,7 @@ class SQLiteConnector extends Connector implements ConnectorInterface
|
||||
* @param array $config
|
||||
* @return \PDO
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \Illuminate\Database\SQLiteDatabaseDoesNotExistException
|
||||
*/
|
||||
public function connect(array $config)
|
||||
{
|
||||
@@ -21,7 +21,7 @@ class SQLiteConnector extends Connector implements ConnectorInterface
|
||||
// SQLite supports "in-memory" databases that only last as long as the owning
|
||||
// connection does. These are useful for tests or for short lifetime store
|
||||
// querying. In-memory databases may only have a single open connection.
|
||||
if ($config['database'] == ':memory:') {
|
||||
if ($config['database'] === ':memory:') {
|
||||
return $this->createConnection('sqlite::memory:', $config, $options);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class SQLiteConnector extends Connector implements ConnectorInterface
|
||||
// as the developer probably wants to know if the database exists and this
|
||||
// SQLite driver will not throw any exception if it does not by default.
|
||||
if ($path === false) {
|
||||
throw new InvalidArgumentException("Database (${config['database']}) does not exist.");
|
||||
throw new SQLiteDatabaseDoesNotExistException($config['database']);
|
||||
}
|
||||
|
||||
return $this->createConnection("sqlite:{$path}", $config, $options);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use PDO;
|
||||
use Illuminate\Support\Arr;
|
||||
use PDO;
|
||||
|
||||
class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
@@ -29,13 +29,37 @@ class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
$options = $this->getOptions($config);
|
||||
|
||||
return $this->createConnection($this->getDsn($config), $config, $options);
|
||||
$connection = $this->createConnection($this->getDsn($config), $config, $options);
|
||||
|
||||
$this->configureIsolationLevel($connection, $config);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection transaction isolation level.
|
||||
*
|
||||
* https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureIsolationLevel($connection, array $config)
|
||||
{
|
||||
if (! isset($config['isolation_level'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare(
|
||||
"SET TRANSACTION ISOLATION LEVEL {$config['isolation_level']}"
|
||||
)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DSN string from a configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getDsn(array $config)
|
||||
@@ -43,12 +67,14 @@ class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
// First we will create the basic DSN setup as well as the port if it is in
|
||||
// in the configuration options. This will give us the basic DSN we will
|
||||
// need to establish the PDO connections and return them back for use.
|
||||
if (in_array('dblib', $this->getAvailableDrivers())) {
|
||||
return $this->getDblibDsn($config);
|
||||
} elseif ($this->prefersOdbc($config)) {
|
||||
if ($this->prefersOdbc($config)) {
|
||||
return $this->getOdbcDsn($config);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (in_array('sqlsrv', $this->getAvailableDrivers())) {
|
||||
return $this->getSqlSrvDsn($config);
|
||||
} else {
|
||||
return $this->getDblibDsn($config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +87,7 @@ class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
protected function prefersOdbc(array $config)
|
||||
{
|
||||
return in_array('odbc', $this->getAvailableDrivers()) &&
|
||||
array_get($config, 'odbc') === true;
|
||||
($config['odbc'] ?? null) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +101,7 @@ class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
return $this->buildConnectString('dblib', array_merge([
|
||||
'host' => $this->buildHostString($config, ':'),
|
||||
'dbname' => $config['database'],
|
||||
], Arr::only($config, ['appname', 'charset'])));
|
||||
], Arr::only($config, ['appname', 'charset', 'version'])));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,6 +156,38 @@ class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
$arguments['MultipleActiveResultSets'] = 'false';
|
||||
}
|
||||
|
||||
if (isset($config['transaction_isolation'])) {
|
||||
$arguments['TransactionIsolation'] = $config['transaction_isolation'];
|
||||
}
|
||||
|
||||
if (isset($config['multi_subnet_failover'])) {
|
||||
$arguments['MultiSubnetFailover'] = $config['multi_subnet_failover'];
|
||||
}
|
||||
|
||||
if (isset($config['column_encryption'])) {
|
||||
$arguments['ColumnEncryption'] = $config['column_encryption'];
|
||||
}
|
||||
|
||||
if (isset($config['key_store_authentication'])) {
|
||||
$arguments['KeyStoreAuthentication'] = $config['key_store_authentication'];
|
||||
}
|
||||
|
||||
if (isset($config['key_store_principal_id'])) {
|
||||
$arguments['KeyStorePrincipalId'] = $config['key_store_principal_id'];
|
||||
}
|
||||
|
||||
if (isset($config['key_store_secret'])) {
|
||||
$arguments['KeyStoreSecret'] = $config['key_store_secret'];
|
||||
}
|
||||
|
||||
if (isset($config['login_timeout'])) {
|
||||
$arguments['LoginTimeout'] = $config['login_timeout'];
|
||||
}
|
||||
|
||||
if (isset($config['authentication'])) {
|
||||
$arguments['Authentication'] = $config['authentication'];
|
||||
}
|
||||
|
||||
return $this->buildConnectString('sqlsrv', $arguments);
|
||||
}
|
||||
|
||||
@@ -156,11 +214,11 @@ class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
*/
|
||||
protected function buildHostString(array $config, $separator)
|
||||
{
|
||||
if (isset($config['port']) && ! empty($config['port'])) {
|
||||
return $config['host'].$separator.$config['port'];
|
||||
} else {
|
||||
if (empty($config['port'])) {
|
||||
return $config['host'];
|
||||
}
|
||||
|
||||
return $config['host'].$separator.$config['port'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
246
vendor/laravel/framework/src/Illuminate/Database/Console/DatabaseInspectionCommand.php
vendored
Normal file
246
vendor/laravel/framework/src/Illuminate/Database/Console/DatabaseInspectionCommand.php
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\MySqlConnection;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Composer;
|
||||
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
abstract class DatabaseInspectionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* A map of database column types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $typeMappings = [
|
||||
'bit' => 'string',
|
||||
'citext' => 'string',
|
||||
'enum' => 'string',
|
||||
'geometry' => 'string',
|
||||
'geomcollection' => 'string',
|
||||
'linestring' => 'string',
|
||||
'ltree' => 'string',
|
||||
'multilinestring' => 'string',
|
||||
'multipoint' => 'string',
|
||||
'multipolygon' => 'string',
|
||||
'point' => 'string',
|
||||
'polygon' => 'string',
|
||||
'sysname' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Composer instance.
|
||||
*
|
||||
* @var \Illuminate\Support\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param \Illuminate\Support\Composer|null $composer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Composer $composer = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->composer = $composer ?? $this->laravel->make(Composer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the custom Doctrine type mappings for inspection commands.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
|
||||
* @return void
|
||||
*/
|
||||
protected function registerTypeMappings(AbstractPlatform $platform)
|
||||
{
|
||||
foreach ($this->typeMappings as $type => $value) {
|
||||
$platform->registerDoctrineTypeMapping($type, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable platform name for the given platform.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
|
||||
* @param string $database
|
||||
* @return string
|
||||
*/
|
||||
protected function getPlatformName(AbstractPlatform $platform, $database)
|
||||
{
|
||||
return match (class_basename($platform)) {
|
||||
'MySQLPlatform' => 'MySQL <= 5',
|
||||
'MySQL57Platform' => 'MySQL 5.7',
|
||||
'MySQL80Platform' => 'MySQL 8',
|
||||
'PostgreSQL100Platform', 'PostgreSQLPlatform' => 'Postgres',
|
||||
'SqlitePlatform' => 'SQLite',
|
||||
'SQLServerPlatform' => 'SQL Server',
|
||||
'SQLServer2012Platform' => 'SQL Server 2012',
|
||||
default => $database,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a table in bytes.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param string $table
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getTableSize(ConnectionInterface $connection, string $table)
|
||||
{
|
||||
return match (true) {
|
||||
$connection instanceof MySqlConnection => $this->getMySQLTableSize($connection, $table),
|
||||
$connection instanceof PostgresConnection => $this->getPostgresTableSize($connection, $table),
|
||||
$connection instanceof SQLiteConnection => $this->getSqliteTableSize($connection, $table),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a MySQL table in bytes.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param string $table
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getMySQLTableSize(ConnectionInterface $connection, string $table)
|
||||
{
|
||||
$result = $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [
|
||||
$connection->getDatabaseName(),
|
||||
$table,
|
||||
]);
|
||||
|
||||
return Arr::wrap((array) $result)['size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a Postgres table in bytes.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param string $table
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getPostgresTableSize(ConnectionInterface $connection, string $table)
|
||||
{
|
||||
$result = $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [
|
||||
$table,
|
||||
]);
|
||||
|
||||
return Arr::wrap((array) $result)['size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a SQLite table in bytes.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param string $table
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getSqliteTableSize(ConnectionInterface $connection, string $table)
|
||||
{
|
||||
try {
|
||||
$result = $connection->selectOne('SELECT SUM(pgsize) AS size FROM dbstat WHERE name=?', [
|
||||
$table,
|
||||
]);
|
||||
|
||||
return Arr::wrap((array) $result)['size'];
|
||||
} catch (QueryException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of open connections for a database.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getConnectionCount(ConnectionInterface $connection)
|
||||
{
|
||||
$result = match (true) {
|
||||
$connection instanceof MySqlConnection => $connection->selectOne('show status where variable_name = "threads_connected"'),
|
||||
$connection instanceof PostgresConnection => $connection->selectOne('select count(*) AS "Value" from pg_stat_activity'),
|
||||
$connection instanceof SqlServerConnection => $connection->selectOne('SELECT COUNT(*) Value FROM sys.dm_exec_sessions WHERE status = ?', ['running']),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (! $result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Arr::wrap((array) $result)['Value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection configuration details for the given connection.
|
||||
*
|
||||
* @param string $database
|
||||
* @return array
|
||||
*/
|
||||
protected function getConfigFromDatabase($database)
|
||||
{
|
||||
$database ??= config('database.default');
|
||||
|
||||
return Arr::except(config('database.connections.'.$database), ['password']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the dependencies for the database commands are available.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function ensureDependenciesExist()
|
||||
{
|
||||
return tap(interface_exists('Doctrine\DBAL\Driver'), function ($dependenciesExist) {
|
||||
if (! $dependenciesExist && $this->components->confirm('Inspecting database information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?')) {
|
||||
$this->installDependencies();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the command's dependencies.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Symfony\Component\Process\Exception\ProcessSignaledException
|
||||
*/
|
||||
protected function installDependencies()
|
||||
{
|
||||
$command = collect($this->composer->findComposer())
|
||||
->push('require doctrine/dbal')
|
||||
->implode(' ');
|
||||
|
||||
$process = Process::fromShellCommandline($command, null, null, null, null);
|
||||
|
||||
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) {
|
||||
try {
|
||||
$process->setTty(true);
|
||||
} catch (RuntimeException $e) {
|
||||
$this->components->warn($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$process->run(fn ($type, $line) => $this->output->write($line));
|
||||
} catch (ProcessSignaledException $e) {
|
||||
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
227
vendor/laravel/framework/src/Illuminate/Database/Console/DbCommand.php
vendored
Normal file
227
vendor/laravel/framework/src/Illuminate/Database/Console/DbCommand.php
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\ConfigurationUrlParser;
|
||||
use Symfony\Component\Process\Process;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class DbCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db {connection? : The database connection that should be used}
|
||||
{--read : Connect to the read connection}
|
||||
{--write : Connect to the write connection}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Start a new database CLI session';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
|
||||
if (! isset($connection['host']) && $connection['driver'] !== 'sqlite') {
|
||||
$this->components->error('No host specified for this database connection.');
|
||||
$this->line(' Use the <options=bold>[--read]</> and <options=bold>[--write]</> options to specify a read or write connection.');
|
||||
$this->newLine();
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
(new Process(
|
||||
array_merge([$this->getCommand($connection)], $this->commandArguments($connection)),
|
||||
null,
|
||||
$this->commandEnvironment($connection)
|
||||
))->setTimeout(null)->setTty(true)->mustRun(function ($type, $buffer) {
|
||||
$this->output->write($buffer);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection configuration.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
$connection = $this->laravel['config']['database.connections.'.
|
||||
(($db = $this->argument('connection')) ?? $this->laravel['config']['database.default'])
|
||||
];
|
||||
|
||||
if (empty($connection)) {
|
||||
throw new UnexpectedValueException("Invalid database connection [{$db}].");
|
||||
}
|
||||
|
||||
if (! empty($connection['url'])) {
|
||||
$connection = (new ConfigurationUrlParser)->parseConfiguration($connection);
|
||||
}
|
||||
|
||||
if ($this->option('read')) {
|
||||
if (is_array($connection['read']['host'])) {
|
||||
$connection['read']['host'] = $connection['read']['host'][0];
|
||||
}
|
||||
|
||||
$connection = array_merge($connection, $connection['read']);
|
||||
} elseif ($this->option('write')) {
|
||||
if (is_array($connection['write']['host'])) {
|
||||
$connection['write']['host'] = $connection['write']['host'][0];
|
||||
}
|
||||
|
||||
$connection = array_merge($connection, $connection['write']);
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the database client command.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
public function commandArguments(array $connection)
|
||||
{
|
||||
$driver = ucfirst($connection['driver']);
|
||||
|
||||
return $this->{"get{$driver}Arguments"}($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables for the database client command.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array|null
|
||||
*/
|
||||
public function commandEnvironment(array $connection)
|
||||
{
|
||||
$driver = ucfirst($connection['driver']);
|
||||
|
||||
if (method_exists($this, "get{$driver}Environment")) {
|
||||
return $this->{"get{$driver}Environment"}($connection);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database client command to run.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand(array $connection)
|
||||
{
|
||||
return [
|
||||
'mysql' => 'mysql',
|
||||
'pgsql' => 'psql',
|
||||
'sqlite' => 'sqlite3',
|
||||
'sqlsrv' => 'sqlcmd',
|
||||
][$connection['driver']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the MySQL CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getMysqlArguments(array $connection)
|
||||
{
|
||||
return array_merge([
|
||||
'--host='.$connection['host'],
|
||||
'--port='.$connection['port'],
|
||||
'--user='.$connection['username'],
|
||||
], $this->getOptionalArguments([
|
||||
'password' => '--password='.$connection['password'],
|
||||
'unix_socket' => '--socket='.($connection['unix_socket'] ?? ''),
|
||||
'charset' => '--default-character-set='.($connection['charset'] ?? ''),
|
||||
], $connection), [$connection['database']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the Postgres CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getPgsqlArguments(array $connection)
|
||||
{
|
||||
return [$connection['database']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the SQLite CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getSqliteArguments(array $connection)
|
||||
{
|
||||
return [$connection['database']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the SQL Server CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getSqlsrvArguments(array $connection)
|
||||
{
|
||||
return array_merge(...$this->getOptionalArguments([
|
||||
'database' => ['-d', $connection['database']],
|
||||
'username' => ['-U', $connection['username']],
|
||||
'password' => ['-P', $connection['password']],
|
||||
'host' => ['-S', 'tcp:'.$connection['host']
|
||||
.($connection['port'] ? ','.$connection['port'] : ''), ],
|
||||
], $connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables for the Postgres CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getPgsqlEnvironment(array $connection)
|
||||
{
|
||||
return array_merge(...$this->getOptionalArguments([
|
||||
'username' => ['PGUSER' => $connection['username']],
|
||||
'host' => ['PGHOST' => $connection['host']],
|
||||
'port' => ['PGPORT' => $connection['port']],
|
||||
'password' => ['PGPASSWORD' => $connection['password']],
|
||||
], $connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optional arguments based on the connection configuration.
|
||||
*
|
||||
* @param array $args
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptionalArguments(array $args, array $connection)
|
||||
{
|
||||
return array_values(array_filter($args, function ($key) use ($connection) {
|
||||
return ! empty($connection[$key]);
|
||||
}, ARRAY_FILTER_USE_KEY));
|
||||
}
|
||||
}
|
||||
101
vendor/laravel/framework/src/Illuminate/Database/Console/DumpCommand.php
vendored
Normal file
101
vendor/laravel/framework/src/Illuminate/Database/Console/DumpCommand.php
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Database\Events\SchemaDumped;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'schema:dump')]
|
||||
class DumpCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'schema:dump
|
||||
{--database= : The database connection to use}
|
||||
{--path= : The path where the schema dump file should be stored}
|
||||
{--prune : Delete all existing migration files}';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'schema:dump';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dump the given database schema';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $connections
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher)
|
||||
{
|
||||
$connection = $connections->connection($database = $this->input->getOption('database'));
|
||||
|
||||
$this->schemaState($connection)->dump(
|
||||
$connection, $path = $this->path($connection)
|
||||
);
|
||||
|
||||
$dispatcher->dispatch(new SchemaDumped($connection, $path));
|
||||
|
||||
$info = 'Database schema dumped';
|
||||
|
||||
if ($this->option('prune')) {
|
||||
(new Filesystem)->deleteDirectory(
|
||||
database_path('migrations'), $preserve = false
|
||||
);
|
||||
|
||||
$info .= ' and pruned';
|
||||
}
|
||||
|
||||
$this->components->info($info.' successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a schema state instance for the given connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @return mixed
|
||||
*/
|
||||
protected function schemaState(Connection $connection)
|
||||
{
|
||||
return $connection->getSchemaState()
|
||||
->withMigrationTable($connection->getTablePrefix().Config::get('database.migrations', 'migrations'))
|
||||
->handleOutputUsing(function ($type, $buffer) {
|
||||
$this->output->write($buffer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path that the dump should be written to.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
*/
|
||||
protected function path(Connection $connection)
|
||||
{
|
||||
return tap($this->option('path') ?: database_path('schema/'.$connection->getName().'-schema.dump'), function ($path) {
|
||||
(new Filesystem)->ensureDirectoryExists(dirname($path));
|
||||
});
|
||||
}
|
||||
}
|
||||
154
vendor/laravel/framework/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php
vendored
Normal file
154
vendor/laravel/framework/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Factories;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'make:factory')]
|
||||
class FactoryMakeCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:factory';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'make:factory';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new model factory';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Factory';
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/factory.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.$stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name)));
|
||||
|
||||
$namespaceModel = $this->option('model')
|
||||
? $this->qualifyModel($this->option('model'))
|
||||
: $this->qualifyModel($this->guessModelName($name));
|
||||
|
||||
$model = class_basename($namespaceModel);
|
||||
|
||||
$namespace = $this->getNamespace(
|
||||
Str::replaceFirst($this->rootNamespace(), 'Database\\Factories\\', $this->qualifyClass($this->getNameInput()))
|
||||
);
|
||||
|
||||
$replace = [
|
||||
'{{ factoryNamespace }}' => $namespace,
|
||||
'NamespacedDummyModel' => $namespaceModel,
|
||||
'{{ namespacedModel }}' => $namespaceModel,
|
||||
'{{namespacedModel}}' => $namespaceModel,
|
||||
'DummyModel' => $model,
|
||||
'{{ model }}' => $model,
|
||||
'{{model}}' => $model,
|
||||
'{{ factory }}' => $factory,
|
||||
'{{factory}}' => $factory,
|
||||
];
|
||||
|
||||
return str_replace(
|
||||
array_keys($replace), array_values($replace), parent::buildClass($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination class path.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getPath($name)
|
||||
{
|
||||
$name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '')->finish('Factory');
|
||||
|
||||
return $this->laravel->databasePath().'/factories/'.str_replace('\\', '/', $name).'.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the model name from the Factory name or return a default model name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function guessModelName($name)
|
||||
{
|
||||
if (str_ends_with($name, 'Factory')) {
|
||||
$name = substr($name, 0, -7);
|
||||
}
|
||||
|
||||
$modelName = $this->qualifyModel(Str::after($name, $this->rootNamespace()));
|
||||
|
||||
if (class_exists($modelName)) {
|
||||
return $modelName;
|
||||
}
|
||||
|
||||
if (is_dir(app_path('Models/'))) {
|
||||
return $this->rootNamespace().'Models\Model';
|
||||
}
|
||||
|
||||
return $this->rootNamespace().'Model';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['model', 'm', InputOption::VALUE_OPTIONAL, 'The name of the model'],
|
||||
];
|
||||
}
|
||||
}
|
||||
23
vendor/laravel/framework/src/Illuminate/Database/Console/Factories/stubs/factory.stub
vendored
Normal file
23
vendor/laravel/framework/src/Illuminate/Database/Console/Factories/stubs/factory.stub
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace {{ factoryNamespace }};
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\{{ namespacedModel }}>
|
||||
*/
|
||||
class {{ factory }}Factory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,27 @@ class BaseCommand extends Command
|
||||
// migrations may be run for any customized path from within the application.
|
||||
if ($this->input->hasOption('path') && $this->option('path')) {
|
||||
return collect($this->option('path'))->map(function ($path) {
|
||||
return $this->laravel->basePath().'/'.$path;
|
||||
return ! $this->usingRealPath()
|
||||
? $this->laravel->basePath().'/'.$path
|
||||
: $path;
|
||||
})->all();
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
[$this->getMigrationPath()], $this->migrator->paths()
|
||||
$this->migrator->paths(), [$this->getMigrationPath()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path(s) are pre-resolved "real" paths.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function usingRealPath()
|
||||
{
|
||||
return $this->input->hasOption('realpath') && $this->option('realpath');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the migration directory.
|
||||
*
|
||||
|
||||
120
vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/FreshCommand.php
vendored
Normal file
120
vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/FreshCommand.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\DatabaseRefreshed;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class FreshCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:fresh';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables and re-run all migrations';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
$this->newLine();
|
||||
|
||||
$this->components->task('Dropping all tables', fn () => $this->callSilent('db:wipe', array_filter([
|
||||
'--database' => $database,
|
||||
'--drop-views' => $this->option('drop-views'),
|
||||
'--drop-types' => $this->option('drop-types'),
|
||||
'--force' => true,
|
||||
])) == 0);
|
||||
|
||||
$this->newLine();
|
||||
|
||||
$this->call('migrate', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $this->input->getOption('path'),
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--schema-path' => $this->input->getOption('schema-path'),
|
||||
'--force' => true,
|
||||
'--step' => $this->option('step'),
|
||||
]));
|
||||
|
||||
if ($this->laravel->bound(Dispatcher::class)) {
|
||||
$this->laravel[Dispatcher::class]->dispatch(
|
||||
new DatabaseRefreshed
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->needsSeeding()) {
|
||||
$this->runSeeder($database);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the developer has requested database seeding.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function needsSeeding()
|
||||
{
|
||||
return $this->option('seed') || $this->option('seeder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeder command.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function runSeeder($database)
|
||||
{
|
||||
$this->call('db:seed', array_filter([
|
||||
'--database' => $database,
|
||||
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
|
||||
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
['schema-path', null, InputOption::VALUE_OPTIONAL, 'The path to a schema dump file'],
|
||||
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'],
|
||||
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'],
|
||||
['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
@@ -47,13 +47,13 @@ class InstallCommand extends Command
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->repository->setSource($this->input->getOption('database'));
|
||||
|
||||
$this->repository->createRepository();
|
||||
|
||||
$this->info('Migration table created successfully.');
|
||||
$this->components->info('Migration table created successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ class InstallCommand extends Command
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Console\Isolatable;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\SchemaLoaded;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Database\SQLiteDatabaseDoesNotExistException;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
use PDOException;
|
||||
use Throwable;
|
||||
|
||||
class MigrateCommand extends BaseCommand
|
||||
class MigrateCommand extends BaseCommand implements Isolatable
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
@@ -14,12 +21,15 @@ class MigrateCommand extends BaseCommand
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'migrate {--database= : The database connection to use.}
|
||||
{--force : Force the operation to run when in production.}
|
||||
{--path= : The path of migrations files to be executed.}
|
||||
{--pretend : Dump the SQL queries that would be run.}
|
||||
{--seed : Indicates if the seed task should be re-run.}
|
||||
{--step : Force the migrations to be run so they can be rolled back individually.}';
|
||||
protected $signature = 'migrate {--database= : The database connection to use}
|
||||
{--force : Force the operation to run when in production}
|
||||
{--path=* : The path(s) to the migrations files to be executed}
|
||||
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
|
||||
{--schema-path= : The path to a schema dump file}
|
||||
{--pretend : Dump the SQL queries that would be run}
|
||||
{--seed : Indicates if the seed task should be re-run}
|
||||
{--seeder= : The class name of the root seeder}
|
||||
{--step : Force the migrations to be run so they can be rolled back individually}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -35,53 +45,63 @@ class MigrateCommand extends BaseCommand
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* The event dispatcher instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* Create a new migration command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
public function __construct(Migrator $migrator, Dispatcher $dispatcher)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->prepareDatabase();
|
||||
$this->migrator->usingConnection($this->option('database'), function () {
|
||||
$this->prepareDatabase();
|
||||
|
||||
// Next, we will check to see if a path option has been defined. If it has
|
||||
// we will use the path relative to the root of this installation folder
|
||||
// so that migrations may be run for any path within the applications.
|
||||
$this->migrator->run($this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => $this->option('step'),
|
||||
]);
|
||||
// Next, we will check to see if a path option has been defined. If it has
|
||||
// we will use the path relative to the root of this installation folder
|
||||
// so that migrations may be run for any path within the applications.
|
||||
$migrations = $this->migrator->setOutput($this->output)
|
||||
->run($this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => $this->option('step'),
|
||||
]);
|
||||
|
||||
// Once the migrator has run we will grab the note output and send it out to
|
||||
// the console screen, since the migrator itself functions without having
|
||||
// any instances of the OutputInterface contract passed into the class.
|
||||
foreach ($this->migrator->getNotes() as $note) {
|
||||
$this->output->writeln($note);
|
||||
}
|
||||
// Finally, if the "seed" option has been given, we will re-run the database
|
||||
// seed task to re-populate the database, which is convenient when adding
|
||||
// a migration and a seed at the same time, as it is only this command.
|
||||
if ($this->option('seed') && ! $this->option('pretend')) {
|
||||
$this->call('db:seed', [
|
||||
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
|
||||
'--force' => true,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, if the "seed" option has been given, we will re-run the database
|
||||
// seed task to re-populate the database, which is convenient when adding
|
||||
// a migration and a seed at the same time, as it is only this command.
|
||||
if ($this->option('seed')) {
|
||||
$this->call('db:seed', ['--force' => true]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,12 +111,171 @@ class MigrateCommand extends BaseCommand
|
||||
*/
|
||||
protected function prepareDatabase()
|
||||
{
|
||||
$this->migrator->setConnection($this->option('database'));
|
||||
if (! $this->repositoryExists()) {
|
||||
$this->components->info('Preparing database.');
|
||||
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
$this->call(
|
||||
'migrate:install', ['--database' => $this->option('database')]
|
||||
);
|
||||
$this->components->task('Creating migration table', function () {
|
||||
return $this->callSilent('migrate:install', array_filter([
|
||||
'--database' => $this->option('database'),
|
||||
])) == 0;
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if (! $this->migrator->hasRunAnyMigrations() && ! $this->option('pretend')) {
|
||||
$this->loadSchemaState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the migrator repository exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function repositoryExists()
|
||||
{
|
||||
return retry(2, fn () => $this->migrator->repositoryExists(), 0, function ($e) {
|
||||
try {
|
||||
if ($e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) {
|
||||
return $this->createMissingSqliteDatbase($e->getPrevious()->path);
|
||||
}
|
||||
|
||||
$connection = $this->migrator->resolveConnection($this->option('database'));
|
||||
|
||||
if (
|
||||
$e->getPrevious() instanceof PDOException &&
|
||||
$e->getPrevious()->getCode() === 1049 &&
|
||||
$connection->getDriverName() === 'mysql') {
|
||||
return $this->createMissingMysqlDatabase($connection);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a missing SQLite database.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
protected function createMissingSqliteDatbase($path)
|
||||
{
|
||||
if ($this->option('force')) {
|
||||
return touch($path);
|
||||
}
|
||||
|
||||
if ($this->option('no-interaction')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->components->warn('The SQLite database does not exist: '.$path);
|
||||
|
||||
if (! $this->components->confirm('Would you like to create it?')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return touch($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a missing MySQL database.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function createMissingMysqlDatabase($connection)
|
||||
{
|
||||
if ($this->laravel['config']->get("database.connections.{$connection->getName()}.database") !== $connection->getDatabaseName()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->option('force') && $this->option('no-interaction')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->option('force') && ! $this->option('no-interaction')) {
|
||||
$this->components->warn("The database '{$connection->getDatabaseName()}' does not exist on the '{$connection->getName()}' connection.");
|
||||
|
||||
if (! $this->components->confirm('Would you like to create it?')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->laravel['config']->set("database.connections.{$connection->getName()}.database", null);
|
||||
|
||||
$this->laravel['db']->purge();
|
||||
|
||||
$freshConnection = $this->migrator->resolveConnection($this->option('database'));
|
||||
|
||||
return tap($freshConnection->unprepared("CREATE DATABASE IF NOT EXISTS `{$connection->getDatabaseName()}`"), function () {
|
||||
$this->laravel['db']->purge();
|
||||
});
|
||||
} finally {
|
||||
$this->laravel['config']->set("database.connections.{$connection->getName()}.database", $connection->getDatabaseName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the schema state to seed the initial database schema structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function loadSchemaState()
|
||||
{
|
||||
$connection = $this->migrator->resolveConnection($this->option('database'));
|
||||
|
||||
// First, we will make sure that the connection supports schema loading and that
|
||||
// the schema file exists before we proceed any further. If not, we will just
|
||||
// continue with the standard migration operation as normal without errors.
|
||||
if ($connection instanceof SqlServerConnection ||
|
||||
! is_file($path = $this->schemaPath($connection))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->components->info('Loading stored database schemas.');
|
||||
|
||||
$this->components->task($path, function () use ($connection, $path) {
|
||||
// Since the schema file will create the "migrations" table and reload it to its
|
||||
// proper state, we need to delete it here so we don't get an error that this
|
||||
// table already exists when the stored database schema file gets executed.
|
||||
$this->migrator->deleteRepository();
|
||||
|
||||
$connection->getSchemaState()->handleOutputUsing(function ($type, $buffer) {
|
||||
$this->output->write($buffer);
|
||||
})->load($path);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
|
||||
// Finally, we will fire an event that this schema has been loaded so developers
|
||||
// can perform any post schema load tasks that are necessary in listeners for
|
||||
// this event, which may seed the database tables with some necessary data.
|
||||
$this->dispatcher->dispatch(
|
||||
new SchemaLoaded($connection, $path)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the stored schema for the given connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @return string
|
||||
*/
|
||||
protected function schemaPath($connection)
|
||||
{
|
||||
if ($this->option('schema-path')) {
|
||||
return $this->option('schema-path');
|
||||
}
|
||||
|
||||
if (file_exists($path = database_path('schema/'.$connection->getName().'-schema.dump'))) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return database_path('schema/'.$connection->getName().'-schema.sql');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Support\Composer;
|
||||
use Illuminate\Database\Migrations\MigrationCreator;
|
||||
use Illuminate\Support\Composer;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MigrateMakeCommand extends BaseCommand
|
||||
{
|
||||
@@ -12,10 +13,12 @@ class MigrateMakeCommand extends BaseCommand
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'make:migration {name : The name of the migration.}
|
||||
{--create= : The table to be created.}
|
||||
{--table= : The table to migrate.}
|
||||
{--path= : The location where the migration file should be created.}';
|
||||
protected $signature = 'make:migration {name : The name of the migration}
|
||||
{--create= : The table to be created}
|
||||
{--table= : The table to migrate}
|
||||
{--path= : The location where the migration file should be created}
|
||||
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
|
||||
{--fullpath : Output the full path of the migration}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -58,12 +61,12 @@ class MigrateMakeCommand extends BaseCommand
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
// It's possible for the developer to specify the tables to modify in this
|
||||
// schema operation. The developer may also specify if this table needs
|
||||
// to be freshly created so we can create the appropriate migrations.
|
||||
$name = trim($this->input->getArgument('name'));
|
||||
$name = Str::snake(trim($this->input->getArgument('name')));
|
||||
|
||||
$table = $this->input->getOption('table');
|
||||
|
||||
@@ -78,6 +81,13 @@ class MigrateMakeCommand extends BaseCommand
|
||||
$create = true;
|
||||
}
|
||||
|
||||
// Next, we will attempt to guess the table name if this the migration has
|
||||
// "create" in the name. This will allow us to provide a convenient way
|
||||
// of creating migrations that create new tables for the application.
|
||||
if (! $table) {
|
||||
[$table, $create] = TableGuesser::guess($name);
|
||||
}
|
||||
|
||||
// Now we are ready to write the migration out to disk. Once we've written
|
||||
// the migration out, we will dump-autoload for the entire framework to
|
||||
// make sure that the migrations are registered by the class loaders.
|
||||
@@ -91,16 +101,20 @@ class MigrateMakeCommand extends BaseCommand
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $table
|
||||
* @param bool $create
|
||||
* @param bool $create
|
||||
* @return string
|
||||
*/
|
||||
protected function writeMigration($name, $table, $create)
|
||||
{
|
||||
$file = pathinfo($this->creator->create(
|
||||
$file = $this->creator->create(
|
||||
$name, $this->getMigrationPath(), $table, $create
|
||||
), PATHINFO_FILENAME);
|
||||
);
|
||||
|
||||
$this->line("<info>Created Migration:</info> {$file}");
|
||||
if (! $this->option('fullpath')) {
|
||||
$file = pathinfo($file, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
$this->components->info(sprintf('Migration [%s] created successfully.', $file));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +125,9 @@ class MigrateMakeCommand extends BaseCommand
|
||||
protected function getMigrationPath()
|
||||
{
|
||||
if (! is_null($targetPath = $this->input->getOption('path'))) {
|
||||
return $this->laravel->basePath().'/'.$targetPath;
|
||||
return ! $this->usingRealPath()
|
||||
? $this->laravel->basePath().'/'.$targetPath
|
||||
: $targetPath;
|
||||
}
|
||||
|
||||
return parent::getMigrationPath();
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\DatabaseRefreshed;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class RefreshCommand extends Command
|
||||
@@ -27,12 +29,12 @@ class RefreshCommand extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Next we'll gather some of the options so that we can have the right options
|
||||
@@ -42,31 +44,38 @@ class RefreshCommand extends Command
|
||||
|
||||
$path = $this->input->getOption('path');
|
||||
|
||||
$force = $this->input->getOption('force');
|
||||
|
||||
// If the "step" option is specified it means we only want to rollback a small
|
||||
// number of migrations before migrating again. For example, the user might
|
||||
// only rollback and remigrate the latest four migrations instead of all.
|
||||
$step = $this->input->getOption('step') ?: 0;
|
||||
|
||||
if ($step > 0) {
|
||||
$this->runRollback($database, $path, $step, $force);
|
||||
$this->runRollback($database, $path, $step);
|
||||
} else {
|
||||
$this->runReset($database, $path, $force);
|
||||
$this->runReset($database, $path);
|
||||
}
|
||||
|
||||
// The refresh command is essentially just a brief aggregate of a few other of
|
||||
// the migration commands and just provides a convenient wrapper to execute
|
||||
// them in succession. We'll also see if we need to re-seed the database.
|
||||
$this->call('migrate', [
|
||||
$this->call('migrate', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--force' => $force,
|
||||
]);
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
if ($this->laravel->bound(Dispatcher::class)) {
|
||||
$this->laravel[Dispatcher::class]->dispatch(
|
||||
new DatabaseRefreshed
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->needsSeeding()) {
|
||||
$this->runSeeder($database);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,18 +83,18 @@ class RefreshCommand extends Command
|
||||
*
|
||||
* @param string $database
|
||||
* @param string $path
|
||||
* @param bool $step
|
||||
* @param bool $force
|
||||
* @param int $step
|
||||
* @return void
|
||||
*/
|
||||
protected function runRollback($database, $path, $step, $force)
|
||||
protected function runRollback($database, $path, $step)
|
||||
{
|
||||
$this->call('migrate:rollback', [
|
||||
$this->call('migrate:rollback', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--step' => $step,
|
||||
'--force' => $force,
|
||||
]);
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,16 +102,16 @@ class RefreshCommand extends Command
|
||||
*
|
||||
* @param string $database
|
||||
* @param string $path
|
||||
* @param bool $force
|
||||
* @return void
|
||||
*/
|
||||
protected function runReset($database, $path, $force)
|
||||
protected function runReset($database, $path)
|
||||
{
|
||||
$this->call('migrate:reset', [
|
||||
$this->call('migrate:reset', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--force' => $force,
|
||||
]);
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,11 +132,11 @@ class RefreshCommand extends Command
|
||||
*/
|
||||
protected function runSeeder($database)
|
||||
{
|
||||
$this->call('db:seed', [
|
||||
$this->call('db:seed', array_filter([
|
||||
'--database' => $database,
|
||||
'--class' => $this->option('seeder') ?: 'DatabaseSeeder',
|
||||
'--force' => $this->option('force'),
|
||||
]);
|
||||
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,17 +147,13 @@ class RefreshCommand extends Command
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
|
||||
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
|
||||
|
||||
['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'],
|
||||
|
||||
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'],
|
||||
|
||||
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder.'],
|
||||
|
||||
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted & re-run.'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'],
|
||||
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'],
|
||||
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted & re-run'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,33 +47,26 @@ class ResetCommand extends BaseCommand
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->migrator->setConnection($this->option('database'));
|
||||
return $this->migrator->usingConnection($this->option('database'), function () {
|
||||
// First, we'll make sure that the migration table actually exists before we
|
||||
// start trying to rollback and re-run all of the migrations. If it's not
|
||||
// present we'll just bail out with an info message for the developers.
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
return $this->components->warn('Migration table not found.');
|
||||
}
|
||||
|
||||
// First, we'll make sure that the migration table actually exists before we
|
||||
// start trying to rollback and re-run all of the migrations. If it's not
|
||||
// present we'll just bail out with an info message for the developers.
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
return $this->comment('Migration table not found.');
|
||||
}
|
||||
|
||||
$this->migrator->reset(
|
||||
$this->getMigrationPaths(), $this->option('pretend')
|
||||
);
|
||||
|
||||
// Once the migrator has run we will grab the note output and send it out to
|
||||
// the console screen, since the migrator itself functions without having
|
||||
// any instances of the OutputInterface contract passed into the class.
|
||||
foreach ($this->migrator->getNotes() as $note) {
|
||||
$this->output->writeln($note);
|
||||
}
|
||||
$this->migrator->setOutput($this->output)->reset(
|
||||
$this->getMigrationPaths(), $this->option('pretend')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,13 +77,15 @@ class ResetCommand extends BaseCommand
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) of migrations files to be executed.'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
|
||||
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
|
||||
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,29 +47,24 @@ class RollbackCommand extends BaseCommand
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->migrator->setConnection($this->option('database'));
|
||||
$this->migrator->usingConnection($this->option('database'), function () {
|
||||
$this->migrator->setOutput($this->output)->rollback(
|
||||
$this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => (int) $this->option('step'),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
$this->migrator->rollback(
|
||||
$this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => (int) $this->option('step'),
|
||||
]
|
||||
);
|
||||
|
||||
// Once the migrator has run we will grab the note output and send it out to
|
||||
// the console screen, since the migrator itself functions without having
|
||||
// any instances of the OutputInterface contract passed into the class.
|
||||
foreach ($this->migrator->getNotes() as $note) {
|
||||
$this->output->writeln($note);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,15 +75,17 @@ class RollbackCommand extends BaseCommand
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
|
||||
['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
|
||||
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
|
||||
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted.'],
|
||||
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'],
|
||||
|
||||
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class StatusCommand extends BaseCommand
|
||||
@@ -32,8 +32,8 @@ class StatusCommand extends BaseCommand
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return \Illuminate\Database\Console\Migrations\StatusCommand
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
@@ -45,40 +45,59 @@ class StatusCommand extends BaseCommand
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int|null
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->migrator->setConnection($this->option('database'));
|
||||
return $this->migrator->usingConnection($this->option('database'), function () {
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
$this->components->error('Migration table not found.');
|
||||
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
return $this->error('No migrations found.');
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
$ran = $this->migrator->getRepository()->getRan();
|
||||
$ran = $this->migrator->getRepository()->getRan();
|
||||
|
||||
if (count($migrations = $this->getStatusFor($ran)) > 0) {
|
||||
$this->table(['Ran?', 'Migration'], $migrations);
|
||||
} else {
|
||||
$this->error('No migrations found');
|
||||
}
|
||||
$batches = $this->migrator->getRepository()->getMigrationBatches();
|
||||
|
||||
if (count($migrations = $this->getStatusFor($ran, $batches)) > 0) {
|
||||
$this->newLine();
|
||||
|
||||
$this->components->twoColumnDetail('<fg=gray>Migration name</>', '<fg=gray>Batch / Status</>');
|
||||
|
||||
$migrations->each(
|
||||
fn ($migration) => $this->components->twoColumnDetail($migration[0], $migration[1])
|
||||
);
|
||||
|
||||
$this->newLine();
|
||||
} else {
|
||||
$this->components->info('No migrations found');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status for the given ran migrations.
|
||||
* Get the status for the given run migrations.
|
||||
*
|
||||
* @param array $ran
|
||||
* @param array $batches
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getStatusFor(array $ran)
|
||||
protected function getStatusFor(array $ran, array $batches)
|
||||
{
|
||||
return Collection::make($this->getAllMigrationFiles())
|
||||
->map(function ($migration) use ($ran) {
|
||||
->map(function ($migration) use ($ran, $batches) {
|
||||
$migrationName = $this->migrator->getMigrationName($migration);
|
||||
|
||||
return in_array($migrationName, $ran)
|
||||
? ['<info>Y</info>', $migrationName]
|
||||
: ['<fg=red>N</fg=red>', $migrationName];
|
||||
$status = in_array($migrationName, $ran)
|
||||
? '<fg=green;options=bold>Ran</>'
|
||||
: '<fg=yellow;options=bold>Pending</>';
|
||||
|
||||
if (in_array($migrationName, $ran)) {
|
||||
$status = '['.$batches[$migrationName].'] '.$status;
|
||||
}
|
||||
|
||||
return [$migrationName, $status];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,9 +119,11 @@ class StatusCommand extends BaseCommand
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
|
||||
['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to use.'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to use'],
|
||||
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
37
vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/TableGuesser.php
vendored
Normal file
37
vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/TableGuesser.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
class TableGuesser
|
||||
{
|
||||
const CREATE_PATTERNS = [
|
||||
'/^create_(\w+)_table$/',
|
||||
'/^create_(\w+)$/',
|
||||
];
|
||||
|
||||
const CHANGE_PATTERNS = [
|
||||
'/_(to|from|in)_(\w+)_table$/',
|
||||
'/_(to|from|in)_(\w+)$/',
|
||||
];
|
||||
|
||||
/**
|
||||
* Attempt to guess the table name and "creation" status of the given migration.
|
||||
*
|
||||
* @param string $migration
|
||||
* @return array
|
||||
*/
|
||||
public static function guess($migration)
|
||||
{
|
||||
foreach (self::CREATE_PATTERNS as $pattern) {
|
||||
if (preg_match($pattern, $migration, $matches)) {
|
||||
return [$matches[1], $create = true];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::CHANGE_PATTERNS as $pattern) {
|
||||
if (preg_match($pattern, $migration, $matches)) {
|
||||
return [$matches[2], $create = false];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
151
vendor/laravel/framework/src/Illuminate/Database/Console/MonitorCommand.php
vendored
Normal file
151
vendor/laravel/framework/src/Illuminate/Database/Console/MonitorCommand.php
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Database\Events\DatabaseBusy;
|
||||
use Illuminate\Support\Composer;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'db:monitor')]
|
||||
class MonitorCommand extends DatabaseInspectionCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:monitor
|
||||
{--databases= : The database connections to monitor}
|
||||
{--max= : The maximum number of connections that can be open before an event is dispatched}';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'db:monitor';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Monitor the number of connections on the specified database';
|
||||
|
||||
/**
|
||||
* The connection resolver instance.
|
||||
*
|
||||
* @var \Illuminate\Database\ConnectionResolverInterface
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The events dispatcher instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $connection
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @param \Illuminate\Support\Composer $composer
|
||||
*/
|
||||
public function __construct(ConnectionResolverInterface $connection, Dispatcher $events, Composer $composer)
|
||||
{
|
||||
parent::__construct($composer);
|
||||
|
||||
$this->connection = $connection;
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$databases = $this->parseDatabases($this->option('databases'));
|
||||
|
||||
$this->displayConnections($databases);
|
||||
|
||||
if ($this->option('max')) {
|
||||
$this->dispatchEvents($databases);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the database into an array of the connections.
|
||||
*
|
||||
* @param string $databases
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function parseDatabases($databases)
|
||||
{
|
||||
return collect(explode(',', $databases))->map(function ($database) {
|
||||
if (! $database) {
|
||||
$database = $this->laravel['config']['database.default'];
|
||||
}
|
||||
|
||||
$maxConnections = $this->option('max');
|
||||
|
||||
return [
|
||||
'database' => $database,
|
||||
'connections' => $connections = $this->getConnectionCount($this->connection->connection($database)),
|
||||
'status' => $maxConnections && $connections >= $maxConnections ? '<fg=yellow;options=bold>ALERT</>' : '<fg=green;options=bold>OK</>',
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the databases and their connection counts in the console.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $databases
|
||||
* @return void
|
||||
*/
|
||||
protected function displayConnections($databases)
|
||||
{
|
||||
$this->newLine();
|
||||
|
||||
$this->components->twoColumnDetail('<fg=gray>Database name</>', '<fg=gray>Connections</>');
|
||||
|
||||
$databases->each(function ($database) {
|
||||
$status = '['.$database['connections'].'] '.$database['status'];
|
||||
|
||||
$this->components->twoColumnDetail($database['database'], $status);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the database monitoring events.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $databases
|
||||
* @return void
|
||||
*/
|
||||
protected function dispatchEvents($databases)
|
||||
{
|
||||
$databases->each(function ($database) {
|
||||
if ($database['status'] === '<fg=green;options=bold>OK</>') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->events->dispatch(
|
||||
new DatabaseBusy(
|
||||
$database['database'],
|
||||
$database['connections']
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
186
vendor/laravel/framework/src/Illuminate/Database/Console/PruneCommand.php
vendored
Normal file
186
vendor/laravel/framework/src/Illuminate/Database/Console/PruneCommand.php
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\MassPrunable;
|
||||
use Illuminate\Database\Eloquent\Prunable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class PruneCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'model:prune
|
||||
{--model=* : Class names of the models to be pruned}
|
||||
{--except=* : Class names of the models to be excluded from pruning}
|
||||
{--chunk=1000 : The number of models to retrieve per chunk of models to be deleted}
|
||||
{--pretend : Display the number of prunable records found instead of deleting them}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Prune models that are no longer needed';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Dispatcher $events)
|
||||
{
|
||||
$models = $this->models();
|
||||
|
||||
if ($models->isEmpty()) {
|
||||
$this->components->info('No prunable models found.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->option('pretend')) {
|
||||
$models->each(function ($model) {
|
||||
$this->pretendToPrune($model);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$pruning = [];
|
||||
|
||||
$events->listen(ModelsPruned::class, function ($event) use (&$pruning) {
|
||||
if (! in_array($event->model, $pruning)) {
|
||||
$pruning[] = $event->model;
|
||||
|
||||
$this->newLine();
|
||||
|
||||
$this->components->info(sprintf('Pruning [%s] records.', $event->model));
|
||||
}
|
||||
|
||||
$this->components->twoColumnDetail($event->model, "{$event->count} records");
|
||||
});
|
||||
|
||||
$models->each(function ($model) {
|
||||
$this->pruneModel($model);
|
||||
});
|
||||
|
||||
$events->forget(ModelsPruned::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune the given model.
|
||||
*
|
||||
* @param string $model
|
||||
* @return void
|
||||
*/
|
||||
protected function pruneModel(string $model)
|
||||
{
|
||||
$instance = new $model;
|
||||
|
||||
$chunkSize = property_exists($instance, 'prunableChunkSize')
|
||||
? $instance->prunableChunkSize
|
||||
: $this->option('chunk');
|
||||
|
||||
$total = $this->isPrunable($model)
|
||||
? $instance->pruneAll($chunkSize)
|
||||
: 0;
|
||||
|
||||
if ($total == 0) {
|
||||
$this->components->info("No prunable [$model] records found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the models that should be pruned.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function models()
|
||||
{
|
||||
if (! empty($models = $this->option('model'))) {
|
||||
return collect($models)->filter(function ($model) {
|
||||
return class_exists($model);
|
||||
})->values();
|
||||
}
|
||||
|
||||
$except = $this->option('except');
|
||||
|
||||
if (! empty($models) && ! empty($except)) {
|
||||
throw new InvalidArgumentException('The --models and --except options cannot be combined.');
|
||||
}
|
||||
|
||||
return collect((new Finder)->in($this->getDefaultPath())->files()->name('*.php'))
|
||||
->map(function ($model) {
|
||||
$namespace = $this->laravel->getNamespace();
|
||||
|
||||
return $namespace.str_replace(
|
||||
['/', '.php'],
|
||||
['\\', ''],
|
||||
Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR)
|
||||
);
|
||||
})->when(! empty($except), function ($models) use ($except) {
|
||||
return $models->reject(function ($model) use ($except) {
|
||||
return in_array($model, $except);
|
||||
});
|
||||
})->filter(function ($model) {
|
||||
return $this->isPrunable($model);
|
||||
})->filter(function ($model) {
|
||||
return class_exists($model);
|
||||
})->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default path where models are located.
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function getDefaultPath()
|
||||
{
|
||||
return app_path('Models');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given model class is prunable.
|
||||
*
|
||||
* @param string $model
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPrunable($model)
|
||||
{
|
||||
$uses = class_uses_recursive($model);
|
||||
|
||||
return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display how many models will be pruned.
|
||||
*
|
||||
* @param string $model
|
||||
* @return void
|
||||
*/
|
||||
protected function pretendToPrune($model)
|
||||
{
|
||||
$instance = new $model;
|
||||
|
||||
$count = $instance->prunable()
|
||||
->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($instance))), function ($query) {
|
||||
$query->withTrashed();
|
||||
})->count();
|
||||
|
||||
if ($count === 0) {
|
||||
$this->components->info("No prunable [$model] records found.");
|
||||
} else {
|
||||
$this->components->info("{$count} [{$model}] records will be pruned.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,14 @@
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Illuminate\Database\ConnectionResolverInterface as Resolver;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'db:seed')]
|
||||
class SeedCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
@@ -19,6 +22,17 @@ class SeedCommand extends Command
|
||||
*/
|
||||
protected $name = 'db:seed';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'db:seed';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
@@ -49,19 +63,29 @@ class SeedCommand extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
* @return int
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->components->info('Seeding database.');
|
||||
|
||||
$previousConnection = $this->resolver->getDefaultConnection();
|
||||
|
||||
$this->resolver->setDefaultConnection($this->getDatabase());
|
||||
|
||||
Model::unguarded(function () {
|
||||
$this->getSeeder()->__invoke();
|
||||
});
|
||||
|
||||
if ($previousConnection) {
|
||||
$this->resolver->setDefaultConnection($previousConnection);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,9 +95,20 @@ class SeedCommand extends Command
|
||||
*/
|
||||
protected function getSeeder()
|
||||
{
|
||||
$class = $this->laravel->make($this->input->getOption('class'));
|
||||
$class = $this->input->getArgument('class') ?? $this->input->getOption('class');
|
||||
|
||||
return $class->setContainer($this->laravel)->setCommand($this);
|
||||
if (! str_contains($class, '\\')) {
|
||||
$class = 'Database\\Seeders\\'.$class;
|
||||
}
|
||||
|
||||
if ($class === 'Database\\Seeders\\DatabaseSeeder' &&
|
||||
! class_exists($class)) {
|
||||
$class = 'DatabaseSeeder';
|
||||
}
|
||||
|
||||
return $this->laravel->make($class)
|
||||
->setContainer($this->laravel)
|
||||
->setCommand($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +123,18 @@ class SeedCommand extends Command
|
||||
return $database ?: $this->laravel['config']['database.default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getArguments()
|
||||
{
|
||||
return [
|
||||
['class', InputArgument::OPTIONAL, 'The class name of the root seeder', null],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
@@ -96,11 +143,9 @@ class SeedCommand extends Command
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'DatabaseSeeder'],
|
||||
|
||||
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'],
|
||||
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Support\Composer;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'make:seeder')]
|
||||
class SeederMakeCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
@@ -15,6 +16,17 @@ class SeederMakeCommand extends GeneratorCommand
|
||||
*/
|
||||
protected $name = 'make:seeder';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'make:seeder';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
@@ -29,37 +41,14 @@ class SeederMakeCommand extends GeneratorCommand
|
||||
*/
|
||||
protected $type = 'Seeder';
|
||||
|
||||
/**
|
||||
* The Composer instance.
|
||||
*
|
||||
* @var \Illuminate\Support\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param \Illuminate\Filesystem\Filesystem $files
|
||||
* @param \Illuminate\Support\Composer $composer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Filesystem $files, Composer $composer)
|
||||
{
|
||||
parent::__construct($files);
|
||||
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
parent::fire();
|
||||
|
||||
$this->composer->dumpAutoloads();
|
||||
parent::handle();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +58,20 @@ class SeederMakeCommand extends GeneratorCommand
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return __DIR__.'/stubs/seeder.stub';
|
||||
return $this->resolveStubPath('/stubs/seeder.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.$stub;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,17 +82,22 @@ class SeederMakeCommand extends GeneratorCommand
|
||||
*/
|
||||
protected function getPath($name)
|
||||
{
|
||||
return $this->laravel->databasePath().'/seeds/'.$name.'.php';
|
||||
$name = str_replace('\\', '/', Str::replaceFirst($this->rootNamespace(), '', $name));
|
||||
|
||||
if (is_dir($this->laravel->databasePath().'/seeds')) {
|
||||
return $this->laravel->databasePath().'/seeds/'.$name.'.php';
|
||||
}
|
||||
|
||||
return $this->laravel->databasePath().'/seeders/'.$name.'.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the class name and format according to the root namespace.
|
||||
* Get the root namespace for the class.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function qualifyClass($name)
|
||||
protected function rootNamespace()
|
||||
{
|
||||
return $name;
|
||||
return 'Database\Seeders\\';
|
||||
}
|
||||
}
|
||||
|
||||
19
vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/WithoutModelEvents.php
vendored
Normal file
19
vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/WithoutModelEvents.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
trait WithoutModelEvents
|
||||
{
|
||||
/**
|
||||
* Prevent model events from being dispatched by the given callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return callable
|
||||
*/
|
||||
public function withoutModelEvents(callable $callback)
|
||||
{
|
||||
return fn () => Model::withoutEvents($callback);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DummyClass extends Seeder
|
||||
class {{ class }} extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
|
||||
189
vendor/laravel/framework/src/Illuminate/Database/Console/ShowCommand.php
vendored
Normal file
189
vendor/laravel/framework/src/Illuminate/Database/Console/ShowCommand.php
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\View;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Support\Arr;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'db:show')]
|
||||
class ShowCommand extends DatabaseInspectionCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:show {--database= : The database connection}
|
||||
{--json : Output the database information as JSON}
|
||||
{--counts : Show the table row count <bg=red;options=bold> Note: This can be slow on large databases </>};
|
||||
{--views : Show the database views <bg=red;options=bold> Note: This can be slow on large databases </>}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display information about the given database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $connections
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ConnectionResolverInterface $connections)
|
||||
{
|
||||
if (! $this->ensureDependenciesExist()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$connection = $connections->connection($database = $this->input->getOption('database'));
|
||||
|
||||
$schema = $connection->getDoctrineSchemaManager();
|
||||
|
||||
$this->registerTypeMappings($schema->getDatabasePlatform());
|
||||
|
||||
$data = [
|
||||
'platform' => [
|
||||
'config' => $this->getConfigFromDatabase($database),
|
||||
'name' => $this->getPlatformName($schema->getDatabasePlatform(), $database),
|
||||
'open_connections' => $this->getConnectionCount($connection),
|
||||
],
|
||||
'tables' => $this->tables($connection, $schema),
|
||||
];
|
||||
|
||||
if ($this->option('views')) {
|
||||
$data['views'] = $this->collectViews($connection, $schema);
|
||||
}
|
||||
|
||||
$this->display($data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information regarding the tables within the database.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function tables(ConnectionInterface $connection, AbstractSchemaManager $schema)
|
||||
{
|
||||
return collect($schema->listTables())->map(fn (Table $table, $index) => [
|
||||
'table' => $table->getName(),
|
||||
'size' => $this->getTableSize($connection, $table->getName()),
|
||||
'rows' => $this->option('counts') ? $connection->table($table->getName())->count() : null,
|
||||
'engine' => rescue(fn () => $table->getOption('engine'), null, false),
|
||||
'comment' => $table->getComment(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information regarding the views within the database.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function collectViews(ConnectionInterface $connection, AbstractSchemaManager $schema)
|
||||
{
|
||||
return collect($schema->listViews())
|
||||
->reject(fn (View $view) => str($view->getName())
|
||||
->startsWith(['pg_catalog', 'information_schema', 'spt_']))
|
||||
->map(fn (View $view) => [
|
||||
'view' => $view->getName(),
|
||||
'rows' => $connection->table($view->getName())->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the database information.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function display(array $data)
|
||||
{
|
||||
$this->option('json') ? $this->displayJson($data) : $this->displayForCli($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the database information as JSON.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function displayJson(array $data)
|
||||
{
|
||||
$this->output->writeln(json_encode($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the database information formatted for the CLI.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function displayForCli(array $data)
|
||||
{
|
||||
$platform = $data['platform'];
|
||||
$tables = $data['tables'];
|
||||
$views = $data['views'] ?? null;
|
||||
|
||||
$this->newLine();
|
||||
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>'.$platform['name'].'</>');
|
||||
$this->components->twoColumnDetail('Database', Arr::get($platform['config'], 'database'));
|
||||
$this->components->twoColumnDetail('Host', Arr::get($platform['config'], 'host'));
|
||||
$this->components->twoColumnDetail('Port', Arr::get($platform['config'], 'port'));
|
||||
$this->components->twoColumnDetail('Username', Arr::get($platform['config'], 'username'));
|
||||
$this->components->twoColumnDetail('URL', Arr::get($platform['config'], 'url'));
|
||||
$this->components->twoColumnDetail('Open Connections', $platform['open_connections']);
|
||||
$this->components->twoColumnDetail('Tables', $tables->count());
|
||||
|
||||
if ($tableSizeSum = $tables->sum('size')) {
|
||||
$this->components->twoColumnDetail('Total Size', number_format($tableSizeSum / 1024 / 1024, 2).'MiB');
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($tables->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>Table</>', 'Size (MiB)'.($this->option('counts') ? ' <fg=gray;options=bold>/</> <fg=yellow;options=bold>Rows</>' : ''));
|
||||
|
||||
$tables->each(function ($table) {
|
||||
if ($tableSize = $table['size']) {
|
||||
$tableSize = number_format($tableSize / 1024 / 1024, 2);
|
||||
}
|
||||
|
||||
$this->components->twoColumnDetail(
|
||||
$table['table'].($this->output->isVerbose() ? ' <fg=gray>'.$table['engine'].'</>' : null),
|
||||
($tableSize ? $tableSize : '—').($this->option('counts') ? ' <fg=gray;options=bold>/</> <fg=yellow;options=bold>'.number_format($table['rows']).'</>' : '')
|
||||
);
|
||||
|
||||
if ($this->output->isVerbose()) {
|
||||
if ($table['comment']) {
|
||||
$this->components->bulletList([
|
||||
$table['comment'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($views && $views->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>View</>', '<fg=green;options=bold>Rows</>');
|
||||
|
||||
$views->each(fn ($view) => $this->components->twoColumnDetail($view['view'], number_format($view['rows'])));
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
246
vendor/laravel/framework/src/Illuminate/Database/Console/TableCommand.php
vendored
Normal file
246
vendor/laravel/framework/src/Illuminate/Database/Console/TableCommand.php
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'db:table')]
|
||||
class TableCommand extends DatabaseInspectionCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:table
|
||||
{table? : The name of the table}
|
||||
{--database= : The database connection}
|
||||
{--json : Output the table information as JSON}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display information about the given database table';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ConnectionResolverInterface $connections)
|
||||
{
|
||||
if (! $this->ensureDependenciesExist()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$connection = $connections->connection($this->input->getOption('database'));
|
||||
|
||||
$schema = $connection->getDoctrineSchemaManager();
|
||||
|
||||
$this->registerTypeMappings($schema->getDatabasePlatform());
|
||||
|
||||
$table = $this->argument('table') ?: $this->components->choice(
|
||||
'Which table would you like to inspect?',
|
||||
collect($schema->listTables())->flatMap(fn (Table $table) => [$table->getName()])->toArray()
|
||||
);
|
||||
|
||||
if (! $schema->tablesExist([$table])) {
|
||||
return $this->components->warn("Table [{$table}] doesn't exist.");
|
||||
}
|
||||
|
||||
$table = $schema->listTableDetails($table);
|
||||
|
||||
$columns = $this->columns($table);
|
||||
$indexes = $this->indexes($table);
|
||||
$foreignKeys = $this->foreignKeys($table);
|
||||
|
||||
$data = [
|
||||
'table' => [
|
||||
'name' => $table->getName(),
|
||||
'columns' => $columns->count(),
|
||||
'size' => $this->getTableSize($connection, $table->getName()),
|
||||
],
|
||||
'columns' => $columns,
|
||||
'indexes' => $indexes,
|
||||
'foreign_keys' => $foreignKeys,
|
||||
];
|
||||
|
||||
$this->display($data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the information regarding the table's columns.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Schema\Table $table
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function columns(Table $table)
|
||||
{
|
||||
return collect($table->getColumns())->map(fn (Column $column) => [
|
||||
'column' => $column->getName(),
|
||||
'attributes' => $this->getAttributesForColumn($column),
|
||||
'default' => $column->getDefault(),
|
||||
'type' => $column->getType()->getName(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes for a table column.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Schema\Column $column
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getAttributesForColumn(Column $column)
|
||||
{
|
||||
return collect([
|
||||
$column->getAutoincrement() ? 'autoincrement' : null,
|
||||
'type' => $column->getType()->getName(),
|
||||
$column->getUnsigned() ? 'unsigned' : null,
|
||||
! $column->getNotNull() ? 'nullable' : null,
|
||||
])->filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the information regarding the table's indexes.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Schema\Table $table
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function indexes(Table $table)
|
||||
{
|
||||
return collect($table->getIndexes())->map(fn (Index $index) => [
|
||||
'name' => $index->getName(),
|
||||
'columns' => collect($index->getColumns()),
|
||||
'attributes' => $this->getAttributesForIndex($index),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes for a table index.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Schema\Index $index
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getAttributesForIndex(Index $index)
|
||||
{
|
||||
return collect([
|
||||
'compound' => count($index->getColumns()) > 1,
|
||||
'unique' => $index->isUnique(),
|
||||
'primary' => $index->isPrimary(),
|
||||
])->filter()->keys()->map(fn ($attribute) => Str::lower($attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the information regarding the table's foreign keys.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Schema\Table $table
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function foreignKeys(Table $table)
|
||||
{
|
||||
return collect($table->getForeignKeys())->map(fn (ForeignKeyConstraint $foreignKey) => [
|
||||
'name' => $foreignKey->getName(),
|
||||
'local_table' => $table->getName(),
|
||||
'local_columns' => collect($foreignKey->getLocalColumns()),
|
||||
'foreign_table' => $foreignKey->getForeignTableName(),
|
||||
'foreign_columns' => collect($foreignKey->getForeignColumns()),
|
||||
'on_update' => Str::lower(rescue(fn () => $foreignKey->getOption('onUpdate'), 'N/A')),
|
||||
'on_delete' => Str::lower(rescue(fn () => $foreignKey->getOption('onDelete'), 'N/A')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the table information.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function display(array $data)
|
||||
{
|
||||
$this->option('json') ? $this->displayJson($data) : $this->displayForCli($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the table information as JSON.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function displayJson(array $data)
|
||||
{
|
||||
$this->output->writeln(json_encode($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the table information formatted for the CLI.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function displayForCli(array $data)
|
||||
{
|
||||
[$table, $columns, $indexes, $foreignKeys] = [
|
||||
$data['table'], $data['columns'], $data['indexes'], $data['foreign_keys'],
|
||||
];
|
||||
|
||||
$this->newLine();
|
||||
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>'.$table['name'].'</>');
|
||||
$this->components->twoColumnDetail('Columns', $table['columns']);
|
||||
|
||||
if ($size = $table['size']) {
|
||||
$this->components->twoColumnDetail('Size', number_format($size / 1024 / 1024, 2).'MiB');
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($columns->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>Column</>', 'Type');
|
||||
|
||||
$columns->each(function ($column) {
|
||||
$this->components->twoColumnDetail(
|
||||
$column['column'].' <fg=gray>'.$column['attributes']->implode(', ').'</>',
|
||||
($column['default'] ? '<fg=gray>'.$column['default'].'</> ' : '').''.$column['type'].''
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($indexes->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>Index</>');
|
||||
|
||||
$indexes->each(function ($index) {
|
||||
$this->components->twoColumnDetail(
|
||||
$index['name'].' <fg=gray>'.$index['columns']->implode(', ').'</>',
|
||||
$index['attributes']->implode(', ')
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($foreignKeys->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>Foreign Key</>', 'On Update / On Delete');
|
||||
|
||||
$foreignKeys->each(function ($foreignKey) {
|
||||
$this->components->twoColumnDetail(
|
||||
$foreignKey['name'].' <fg=gray;options=bold>'.$foreignKey['local_columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'</>',
|
||||
$foreignKey['on_update'].' / '.$foreignKey['on_delete'],
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
125
vendor/laravel/framework/src/Illuminate/Database/Console/WipeCommand.php
vendored
Normal file
125
vendor/laravel/framework/src/Illuminate/Database/Console/WipeCommand.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'db:wipe')]
|
||||
class WipeCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:wipe';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'db:wipe';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables, views, and types';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
if ($this->option('drop-views')) {
|
||||
$this->dropAllViews($database);
|
||||
|
||||
$this->components->info('Dropped all views successfully.');
|
||||
}
|
||||
|
||||
$this->dropAllTables($database);
|
||||
|
||||
$this->components->info('Dropped all tables successfully.');
|
||||
|
||||
if ($this->option('drop-types')) {
|
||||
$this->dropAllTypes($database);
|
||||
|
||||
$this->components->info('Dropped all types successfully.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all of the database tables.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function dropAllTables($database)
|
||||
{
|
||||
$this->laravel['db']->connection($database)
|
||||
->getSchemaBuilder()
|
||||
->dropAllTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all of the database views.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function dropAllViews($database)
|
||||
{
|
||||
$this->laravel['db']->connection($database)
|
||||
->getSchemaBuilder()
|
||||
->dropAllViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all of the database types.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function dropAllTypes($database)
|
||||
{
|
||||
$this->laravel['db']->connection($database)
|
||||
->getSchemaBuilder()
|
||||
->dropAllTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
|
||||
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
];
|
||||
}
|
||||
}
|
||||
95
vendor/laravel/framework/src/Illuminate/Database/DBAL/TimestampType.php
vendored
Normal file
95
vendor/laravel/framework/src/Illuminate/Database/DBAL/TimestampType.php
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\DBAL;
|
||||
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\PhpDateTimeMappingType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
class TimestampType extends Type implements PhpDateTimeMappingType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return match ($name = $platform->getName()) {
|
||||
'mysql',
|
||||
'mysql2' => $this->getMySqlPlatformSQLDeclaration($fieldDeclaration),
|
||||
'postgresql',
|
||||
'pgsql',
|
||||
'postgres' => $this->getPostgresPlatformSQLDeclaration($fieldDeclaration),
|
||||
'mssql' => $this->getSqlServerPlatformSQLDeclaration($fieldDeclaration),
|
||||
'sqlite',
|
||||
'sqlite3' => $this->getSQLitePlatformSQLDeclaration($fieldDeclaration),
|
||||
default => throw new DBALException('Invalid platform: '.$name),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for MySQL.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getMySqlPlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
$columnType = 'TIMESTAMP';
|
||||
|
||||
if ($fieldDeclaration['precision']) {
|
||||
$columnType = 'TIMESTAMP('.$fieldDeclaration['precision'].')';
|
||||
}
|
||||
|
||||
$notNull = $fieldDeclaration['notnull'] ?? false;
|
||||
|
||||
if (! $notNull) {
|
||||
return $columnType.' NULL';
|
||||
}
|
||||
|
||||
return $columnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for PostgreSQL.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getPostgresPlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
return 'TIMESTAMP('.(int) $fieldDeclaration['precision'].')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for SQL Server.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getSqlServerPlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
return $fieldDeclaration['precision'] ?? false
|
||||
? 'DATETIME2('.$fieldDeclaration['precision'].')'
|
||||
: 'DATETIME';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for SQLite.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getSQLitePlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
return 'DATETIME';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'timestamp';
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,29 @@
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use PDO;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Illuminate\Database\Connectors\ConnectionFactory;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\ConfigurationUrlParser;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Database\Connection
|
||||
*/
|
||||
class DatabaseManager implements ConnectionResolverInterface
|
||||
{
|
||||
use Macroable {
|
||||
__call as macroCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
* @var \Illuminate\Foundation\Application
|
||||
* @var \Illuminate\Contracts\Foundation\Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
@@ -27,21 +38,35 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
/**
|
||||
* The active connection instances.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, \Illuminate\Database\Connection>
|
||||
*/
|
||||
protected $connections = [];
|
||||
|
||||
/**
|
||||
* The custom connection resolvers.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected $extensions = [];
|
||||
|
||||
/**
|
||||
* The callback to be executed to reconnect to a database.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $reconnector;
|
||||
|
||||
/**
|
||||
* The custom Doctrine column types.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected $doctrineTypes = [];
|
||||
|
||||
/**
|
||||
* Create a new database manager instance.
|
||||
*
|
||||
* @param \Illuminate\Foundation\Application $app
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @param \Illuminate\Database\Connectors\ConnectionFactory $factory
|
||||
* @return void
|
||||
*/
|
||||
@@ -49,17 +74,21 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->factory = $factory;
|
||||
|
||||
$this->reconnector = function ($connection) {
|
||||
$this->reconnect($connection->getNameWithReadWriteType());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database connection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function connection($name = null)
|
||||
{
|
||||
list($database, $type) = $this->parseConnectionName($name);
|
||||
[$database, $type] = $this->parseConnectionName($name);
|
||||
|
||||
$name = $name ?: $database;
|
||||
|
||||
@@ -68,7 +97,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
// set the "fetch mode" for PDO which determines the query return types.
|
||||
if (! isset($this->connections[$name])) {
|
||||
$this->connections[$name] = $this->configure(
|
||||
$connection = $this->makeConnection($database), $type
|
||||
$this->makeConnection($database), $type
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,10 +163,11 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
$connections = $this->app['config']['database.connections'];
|
||||
|
||||
if (is_null($config = Arr::get($connections, $name))) {
|
||||
throw new InvalidArgumentException("Database [$name] not configured.");
|
||||
throw new InvalidArgumentException("Database connection [{$name}] not configured.");
|
||||
}
|
||||
|
||||
return $config;
|
||||
return (new ConfigurationUrlParser)
|
||||
->parseConfiguration($config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +179,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
*/
|
||||
protected function configure(Connection $connection, $type)
|
||||
{
|
||||
$connection = $this->setPdoForType($connection, $type);
|
||||
$connection = $this->setPdoForType($connection, $type)->setReadWriteType($type);
|
||||
|
||||
// First we'll set the fetch mode and a few other dependencies of the database
|
||||
// connection. This method basically just configures and prepares it to get
|
||||
@@ -158,12 +188,16 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
$connection->setEventDispatcher($this->app['events']);
|
||||
}
|
||||
|
||||
if ($this->app->bound('db.transactions')) {
|
||||
$connection->setTransactionManager($this->app['db.transactions']);
|
||||
}
|
||||
|
||||
// Here we'll set a reconnector callback. This reconnector can be any callable
|
||||
// so we will set a Closure to reconnect from this manager with the name of
|
||||
// the connection, which will allow us to reconnect from the connections.
|
||||
$connection->setReconnector(function ($connection) {
|
||||
$this->reconnect($connection->getName());
|
||||
});
|
||||
$connection->setReconnector($this->reconnector);
|
||||
|
||||
$this->registerConfiguredDoctrineTypes($connection);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
@@ -172,24 +206,67 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
* Prepare the read / write mode for database connection instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string $type
|
||||
* @param string|null $type
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
protected function setPdoForType(Connection $connection, $type = null)
|
||||
{
|
||||
if ($type == 'read') {
|
||||
if ($type === 'read') {
|
||||
$connection->setPdo($connection->getReadPdo());
|
||||
} elseif ($type == 'write') {
|
||||
} elseif ($type === 'write') {
|
||||
$connection->setReadPdo($connection->getPdo());
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom Doctrine types with the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @return void
|
||||
*/
|
||||
protected function registerConfiguredDoctrineTypes(Connection $connection): void
|
||||
{
|
||||
foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) {
|
||||
$this->registerDoctrineType($class, $name, $name);
|
||||
}
|
||||
|
||||
foreach ($this->doctrineTypes as $name => [$type, $class]) {
|
||||
$connection->registerDoctrineType($class, $name, $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom Doctrine type.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @return void
|
||||
*
|
||||
* @throws \Doctrine\DBAL\DBALException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function registerDoctrineType(string $class, string $name, string $type): void
|
||||
{
|
||||
if (! class_exists('Doctrine\DBAL\Connection')) {
|
||||
throw new RuntimeException(
|
||||
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
|
||||
);
|
||||
}
|
||||
|
||||
if (! Type::hasType($name)) {
|
||||
Type::addType($name, $class);
|
||||
}
|
||||
|
||||
$this->doctrineTypes[$name] = [$type, $class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the given database and remove from local cache.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function purge($name = null)
|
||||
@@ -204,7 +281,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
/**
|
||||
* Disconnect from the given database.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect($name = null)
|
||||
@@ -217,7 +294,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
/**
|
||||
* Reconnect to the given database.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function reconnect($name = null)
|
||||
@@ -231,6 +308,24 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
return $this->refreshPdoConnections($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default database connection for the callback execution.
|
||||
*
|
||||
* @param string $name
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public function usingConnection($name, callable $callback)
|
||||
{
|
||||
$previousName = $this->getDefaultConnection();
|
||||
|
||||
$this->setDefaultConnection($name);
|
||||
|
||||
return tap($callback(), function () use ($previousName) {
|
||||
$this->setDefaultConnection($previousName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the PDO connections on a given connection.
|
||||
*
|
||||
@@ -239,11 +334,15 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
*/
|
||||
protected function refreshPdoConnections($name)
|
||||
{
|
||||
$fresh = $this->makeConnection($name);
|
||||
[$database, $type] = $this->parseConnectionName($name);
|
||||
|
||||
$fresh = $this->configure(
|
||||
$this->makeConnection($database), $type
|
||||
);
|
||||
|
||||
return $this->connections[$name]
|
||||
->setPdo($fresh->getPdo())
|
||||
->setReadPdo($fresh->getReadPdo());
|
||||
->setPdo($fresh->getRawPdo())
|
||||
->setReadPdo($fresh->getRawReadPdo());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +369,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
/**
|
||||
* Get all of the support drivers.
|
||||
*
|
||||
* @return array
|
||||
* @return string[]
|
||||
*/
|
||||
public function supportedDrivers()
|
||||
{
|
||||
@@ -280,7 +379,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
/**
|
||||
* Get all of the drivers that are actually available.
|
||||
*
|
||||
* @return array
|
||||
* @return string[]
|
||||
*/
|
||||
public function availableDrivers()
|
||||
{
|
||||
@@ -293,7 +392,7 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
/**
|
||||
* Register an extension connection resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name
|
||||
* @param callable $resolver
|
||||
* @return void
|
||||
*/
|
||||
@@ -302,25 +401,64 @@ class DatabaseManager implements ConnectionResolverInterface
|
||||
$this->extensions[$name] = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension connection resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function forgetExtension($name)
|
||||
{
|
||||
unset($this->extensions[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the created connections.
|
||||
*
|
||||
* @return array
|
||||
* @return array<string, \Illuminate\Database\Connection>
|
||||
*/
|
||||
public function getConnections()
|
||||
{
|
||||
return $this->connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database reconnector callback.
|
||||
*
|
||||
* @param callable $reconnector
|
||||
* @return void
|
||||
*/
|
||||
public function setReconnector(callable $reconnector)
|
||||
{
|
||||
$this->reconnector = $reconnector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application instance used by the manager.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
||||
* @return $this
|
||||
*/
|
||||
public function setApplication($app)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically pass methods to the default connection.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (static::hasMacro($method)) {
|
||||
return $this->macroCall($method, $parameters);
|
||||
}
|
||||
|
||||
return $this->connection()->$method(...$parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,21 @@ namespace Illuminate\Database;
|
||||
|
||||
use Faker\Factory as FakerFactory;
|
||||
use Faker\Generator as FakerGenerator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Contracts\Queue\EntityResolver;
|
||||
use Illuminate\Database\Connectors\ConnectionFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\QueueEntityResolver;
|
||||
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class DatabaseServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The array of resolved Faker instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $fakers = [];
|
||||
|
||||
/**
|
||||
* Bootstrap the application events.
|
||||
*
|
||||
@@ -35,9 +41,7 @@ class DatabaseServiceProvider extends ServiceProvider
|
||||
Model::clearBootedModels();
|
||||
|
||||
$this->registerConnectionServices();
|
||||
|
||||
$this->registerEloquentFactory();
|
||||
|
||||
$this->registerQueueableEntityResolver();
|
||||
}
|
||||
|
||||
@@ -65,6 +69,14 @@ class DatabaseServiceProvider extends ServiceProvider
|
||||
$this->app->bind('db.connection', function ($app) {
|
||||
return $app['db']->connection();
|
||||
});
|
||||
|
||||
$this->app->bind('db.schema', function ($app) {
|
||||
return $app['db']->connection()->getSchemaBuilder();
|
||||
});
|
||||
|
||||
$this->app->singleton('db.transactions', function ($app) {
|
||||
return new DatabaseTransactionsManager;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,14 +86,16 @@ class DatabaseServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected function registerEloquentFactory()
|
||||
{
|
||||
$this->app->singleton(FakerGenerator::class, function ($app) {
|
||||
return FakerFactory::create($app['config']->get('app.faker_locale', 'en_US'));
|
||||
});
|
||||
$this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
|
||||
$locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
|
||||
|
||||
$this->app->singleton(EloquentFactory::class, function ($app) {
|
||||
return EloquentFactory::construct(
|
||||
$app->make(FakerGenerator::class), $this->app->databasePath('factories')
|
||||
);
|
||||
if (! isset(static::$fakers[$locale])) {
|
||||
static::$fakers[$locale] = FakerFactory::create($locale);
|
||||
}
|
||||
|
||||
static::$fakers[$locale]->unique(true);
|
||||
|
||||
return static::$fakers[$locale];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
73
vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionRecord.php
vendored
Executable file
73
vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionRecord.php
vendored
Executable file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
class DatabaseTransactionRecord
|
||||
{
|
||||
/**
|
||||
* The name of the database connection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $connection;
|
||||
|
||||
/**
|
||||
* The transaction level.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $level;
|
||||
|
||||
/**
|
||||
* The callbacks that should be executed after committing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $callbacks = [];
|
||||
|
||||
/**
|
||||
* Create a new database transaction record instance.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param int $level
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($connection, $level)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be executed after committing.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public function addCallback($callback)
|
||||
{
|
||||
$this->callbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all of the callbacks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function executeCallbacks()
|
||||
{
|
||||
foreach ($this->callbacks as $callback) {
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the callbacks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCallbacks()
|
||||
{
|
||||
return $this->callbacks;
|
||||
}
|
||||
}
|
||||
133
vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionsManager.php
vendored
Executable file
133
vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionsManager.php
vendored
Executable file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
class DatabaseTransactionsManager
|
||||
{
|
||||
/**
|
||||
* All of the recorded transactions.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $transactions;
|
||||
|
||||
/**
|
||||
* The database transaction that should be ignored by callbacks.
|
||||
*
|
||||
* @var \Illuminate\Database\DatabaseTransactionRecord
|
||||
*/
|
||||
protected $callbacksShouldIgnore;
|
||||
|
||||
/**
|
||||
* Create a new database transactions manager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->transactions = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new database transaction.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param int $level
|
||||
* @return void
|
||||
*/
|
||||
public function begin($connection, $level)
|
||||
{
|
||||
$this->transactions->push(
|
||||
new DatabaseTransactionRecord($connection, $level)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the active database transaction.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param int $level
|
||||
* @return void
|
||||
*/
|
||||
public function rollback($connection, $level)
|
||||
{
|
||||
$this->transactions = $this->transactions->reject(
|
||||
fn ($transaction) => $transaction->connection == $connection && $transaction->level > $level
|
||||
)->values();
|
||||
|
||||
if ($this->transactions->isEmpty()) {
|
||||
$this->callbacksShouldIgnore = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the active database transaction.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return void
|
||||
*/
|
||||
public function commit($connection)
|
||||
{
|
||||
[$forThisConnection, $forOtherConnections] = $this->transactions->partition(
|
||||
fn ($transaction) => $transaction->connection == $connection
|
||||
);
|
||||
|
||||
$this->transactions = $forOtherConnections->values();
|
||||
|
||||
$forThisConnection->map->executeCallbacks();
|
||||
|
||||
if ($this->transactions->isEmpty()) {
|
||||
$this->callbacksShouldIgnore = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a transaction callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public function addCallback($callback)
|
||||
{
|
||||
if ($current = $this->callbackApplicableTransactions()->last()) {
|
||||
return $current->addCallback($callback);
|
||||
}
|
||||
|
||||
$callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that callbacks should ignore the given transaction when determining if they should be executed.
|
||||
*
|
||||
* @param \Illuminate\Database\DatabaseTransactionRecord $transaction
|
||||
* @return $this
|
||||
*/
|
||||
public function callbacksShouldIgnore(DatabaseTransactionRecord $transaction)
|
||||
{
|
||||
$this->callbacksShouldIgnore = $transaction;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transactions that are applicable to callbacks.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function callbackApplicableTransactions()
|
||||
{
|
||||
return $this->transactions->reject(function ($transaction) {
|
||||
return $transaction === $this->callbacksShouldIgnore;
|
||||
})->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the transactions.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getTransactions()
|
||||
{
|
||||
return $this->transactions;
|
||||
}
|
||||
}
|
||||
10
vendor/laravel/framework/src/Illuminate/Database/DeadlockException.php
vendored
Normal file
10
vendor/laravel/framework/src/Illuminate/Database/DeadlockException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use PDOException;
|
||||
|
||||
class DeadlockException extends PDOException
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -2,19 +2,24 @@
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Str;
|
||||
use PDOException;
|
||||
use Throwable;
|
||||
|
||||
trait DetectsDeadlocks
|
||||
trait DetectsConcurrencyErrors
|
||||
{
|
||||
/**
|
||||
* Determine if the given exception was caused by a deadlock.
|
||||
* Determine if the given exception was caused by a concurrency error such as a deadlock or serialization failure.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @return bool
|
||||
*/
|
||||
protected function causedByDeadlock(Exception $e)
|
||||
protected function causedByConcurrencyError(Throwable $e)
|
||||
{
|
||||
if ($e instanceof PDOException && ($e->getCode() === 40001 || $e->getCode() === '40001')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = $e->getMessage();
|
||||
|
||||
return Str::contains($message, [
|
||||
@@ -26,6 +31,7 @@ trait DetectsDeadlocks
|
||||
'A table in the database is locked',
|
||||
'has been chosen as the deadlock victim',
|
||||
'Lock wait timeout exceeded; try restarting transaction',
|
||||
'WSREP detected deadlock/conflict and aborted the transaction. Try restarting the transaction',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
trait DetectsLostConnections
|
||||
{
|
||||
/**
|
||||
* Determine if the given exception was caused by a lost connection.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @return bool
|
||||
*/
|
||||
protected function causedByLostConnection(Exception $e)
|
||||
protected function causedByLostConnection(Throwable $e)
|
||||
{
|
||||
$message = $e->getMessage();
|
||||
|
||||
@@ -29,6 +29,38 @@ trait DetectsLostConnections
|
||||
'Error writing data to the connection',
|
||||
'Resource deadlock avoided',
|
||||
'Transaction() on null',
|
||||
'child connection forced to terminate due to client_idle_limit',
|
||||
'query_wait_timeout',
|
||||
'reset by peer',
|
||||
'Physical connection is not usable',
|
||||
'TCP Provider: Error code 0x68',
|
||||
'ORA-03114',
|
||||
'Packets out of order. Expected',
|
||||
'Adaptive Server connection failed',
|
||||
'Communication link failure',
|
||||
'connection is no longer usable',
|
||||
'Login timeout expired',
|
||||
'SQLSTATE[HY000] [2002] Connection refused',
|
||||
'running with the --read-only option so it cannot execute this statement',
|
||||
'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
|
||||
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
|
||||
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
|
||||
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for',
|
||||
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
|
||||
'SQLSTATE[HY000] [2002] Connection timed out',
|
||||
'SSL: Connection timed out',
|
||||
'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
|
||||
'Temporary failure in name resolution',
|
||||
'SSL: Broken pipe',
|
||||
'SQLSTATE[08S01]: Communication link failure',
|
||||
'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',
|
||||
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',
|
||||
'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',
|
||||
'SQLSTATE[08006] [7] could not translate host name',
|
||||
'TCP Provider: Error code 0x274C',
|
||||
'SQLSTATE[HY000] [2002] No such file or directory',
|
||||
'SSL: Operation timed out',
|
||||
'Reason: Server is in script upgrade mode. Only administrator can connect at this time.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
144
vendor/laravel/framework/src/Illuminate/Database/Eloquent/BroadcastableModelEventOccurred.php
vendored
Normal file
144
vendor/laravel/framework/src/Illuminate/Database/Eloquent/BroadcastableModelEventOccurred.php
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BroadcastableModelEventOccurred implements ShouldBroadcast
|
||||
{
|
||||
use InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* The model instance corresponding to the event.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* The event name (created, updated, etc.).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $event;
|
||||
|
||||
/**
|
||||
* The channels that the event should be broadcast on.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $channels = [];
|
||||
|
||||
/**
|
||||
* The queue connection that should be used to queue the broadcast job.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $connection;
|
||||
|
||||
/**
|
||||
* The queue that should be used to queue the broadcast job.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $queue;
|
||||
|
||||
/**
|
||||
* Indicates whether the job should be dispatched after all database transactions have committed.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
public $afterCommit;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $event
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($model, $event)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* The channels the event should broadcast on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
$channels = empty($this->channels)
|
||||
? ($this->model->broadcastOn($this->event) ?: [])
|
||||
: $this->channels;
|
||||
|
||||
return collect($channels)->map(function ($channel) {
|
||||
return $channel instanceof Model ? new PrivateChannel($channel) : $channel;
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The name the event should broadcast as.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function broadcastAs()
|
||||
{
|
||||
$default = class_basename($this->model).ucfirst($this->event);
|
||||
|
||||
return method_exists($this->model, 'broadcastAs')
|
||||
? ($this->model->broadcastAs($this->event) ?: $default)
|
||||
: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data that should be sent with the broadcasted event.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function broadcastWith()
|
||||
{
|
||||
return method_exists($this->model, 'broadcastWith')
|
||||
? $this->model->broadcastWith($this->event)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually specify the channels the event should broadcast on.
|
||||
*
|
||||
* @param array $channels
|
||||
* @return $this
|
||||
*/
|
||||
public function onChannels(array $channels)
|
||||
{
|
||||
$this->channels = $channels;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the event should be broadcast synchronously.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldBroadcastNow()
|
||||
{
|
||||
return $this->event === 'deleted' &&
|
||||
! method_exists($this->model, 'bootSoftDeletes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function event()
|
||||
{
|
||||
return $this->event;
|
||||
}
|
||||
}
|
||||
197
vendor/laravel/framework/src/Illuminate/Database/Eloquent/BroadcastsEvents.php
vendored
Normal file
197
vendor/laravel/framework/src/Illuminate/Database/Eloquent/BroadcastsEvents.php
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait BroadcastsEvents
|
||||
{
|
||||
/**
|
||||
* Boot the event broadcasting trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootBroadcastsEvents()
|
||||
{
|
||||
static::created(function ($model) {
|
||||
$model->broadcastCreated();
|
||||
});
|
||||
|
||||
static::updated(function ($model) {
|
||||
$model->broadcastUpdated();
|
||||
});
|
||||
|
||||
if (method_exists(static::class, 'bootSoftDeletes')) {
|
||||
static::softDeleted(function ($model) {
|
||||
$model->broadcastTrashed();
|
||||
});
|
||||
|
||||
static::restored(function ($model) {
|
||||
$model->broadcastRestored();
|
||||
});
|
||||
}
|
||||
|
||||
static::deleted(function ($model) {
|
||||
$model->broadcastDeleted();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was created.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastCreated($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('created'), 'created', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was updated.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastUpdated($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('updated'), 'updated', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was trashed.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastTrashed($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('trashed'), 'trashed', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was restored.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastRestored($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('restored'), 'restored', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was deleted.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastDeleted($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('deleted'), 'deleted', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the given event instance if channels are configured for the model event.
|
||||
*
|
||||
* @param mixed $instance
|
||||
* @param string $event
|
||||
* @param mixed $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast|null
|
||||
*/
|
||||
protected function broadcastIfBroadcastChannelsExistForEvent($instance, $event, $channels = null)
|
||||
{
|
||||
if (! static::$isBroadcasting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! empty($this->broadcastOn($event)) || ! empty($channels)) {
|
||||
return broadcast($instance->onChannels(Arr::wrap($channels)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new broadcastable model event event.
|
||||
*
|
||||
* @param string $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function newBroadcastableModelEvent($event)
|
||||
{
|
||||
return tap($this->newBroadcastableEvent($event), function ($event) {
|
||||
$event->connection = property_exists($this, 'broadcastConnection')
|
||||
? $this->broadcastConnection
|
||||
: $this->broadcastConnection();
|
||||
|
||||
$event->queue = property_exists($this, 'broadcastQueue')
|
||||
? $this->broadcastQueue
|
||||
: $this->broadcastQueue();
|
||||
|
||||
$event->afterCommit = property_exists($this, 'broadcastAfterCommit')
|
||||
? $this->broadcastAfterCommit
|
||||
: $this->broadcastAfterCommit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new broadcastable model event for the model.
|
||||
*
|
||||
* @param string $event
|
||||
* @return \Illuminate\Database\Eloquent\BroadcastableModelEventOccurred
|
||||
*/
|
||||
protected function newBroadcastableEvent($event)
|
||||
{
|
||||
return new BroadcastableModelEventOccurred($this, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels that model events should broadcast on.
|
||||
*
|
||||
* @param string $event
|
||||
* @return \Illuminate\Broadcasting\Channel|array
|
||||
*/
|
||||
public function broadcastOn($event)
|
||||
{
|
||||
return [$this];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue connection that should be used to broadcast model events.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function broadcastConnection()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue that should be used to broadcast model events.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function broadcastQueue()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model event broadcast queued job should be dispatched after all transactions are committed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function broadcastAfterCommit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
40
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/ArrayObject.php
vendored
Normal file
40
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/ArrayObject.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use ArrayObject as BaseArrayObject;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use JsonSerializable;
|
||||
|
||||
class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Get a collection containing the underlying array.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
return collect($this->getArrayCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->getArrayCopy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array that should be JSON serialized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->getArrayCopy();
|
||||
}
|
||||
}
|
||||
42
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php
vendored
Normal file
42
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsArrayObject.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
class AsArrayObject implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
if (! isset($attributes[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = json_decode($attributes[$key], true);
|
||||
|
||||
return is_array($data) ? new ArrayObject($data) : null;
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return [$key => json_encode($value)];
|
||||
}
|
||||
|
||||
public function serialize($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return $value->getArrayCopy();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
38
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsCollection.php
vendored
Normal file
38
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsCollection.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AsCollection implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
if (! isset($attributes[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = json_decode($attributes[$key], true);
|
||||
|
||||
return is_array($data) ? new Collection($data) : null;
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return [$key => json_encode($value)];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
45
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsEncryptedArrayObject.php
vendored
Normal file
45
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsEncryptedArrayObject.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class AsEncryptedArrayObject implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
if (isset($attributes[$key])) {
|
||||
return new ArrayObject(json_decode(Crypt::decryptString($attributes[$key]), true));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
if (! is_null($value)) {
|
||||
return [$key => Crypt::encryptString(json_encode($value))];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function serialize($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return ! is_null($value) ? $value->getArrayCopy() : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
41
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php
vendored
Normal file
41
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class AsEncryptedCollection implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
if (isset($attributes[$key])) {
|
||||
return new Collection(json_decode(Crypt::decryptString($attributes[$key]), true));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
if (! is_null($value)) {
|
||||
return [$key => Crypt::encryptString(json_encode($value))];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
32
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsStringable.php
vendored
Normal file
32
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/AsStringable.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AsStringable implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
return isset($value) ? Str::of($value) : null;
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return isset($value) ? (string) $value : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
105
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/Attribute.php
vendored
Normal file
105
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Casts/Attribute.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
class Attribute
|
||||
{
|
||||
/**
|
||||
* The attribute accessor.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $get;
|
||||
|
||||
/**
|
||||
* The attribute mutator.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $set;
|
||||
|
||||
/**
|
||||
* Indicates if caching is enabled for this attribute.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $withCaching = false;
|
||||
|
||||
/**
|
||||
* Indicates if caching of objects is enabled for this attribute.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $withObjectCaching = true;
|
||||
|
||||
/**
|
||||
* Create a new attribute accessor / mutator.
|
||||
*
|
||||
* @param callable|null $get
|
||||
* @param callable|null $set
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(callable $get = null, callable $set = null)
|
||||
{
|
||||
$this->get = $get;
|
||||
$this->set = $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute accessor / mutator.
|
||||
*
|
||||
* @param callable|null $get
|
||||
* @param callable|null $set
|
||||
* @return static
|
||||
*/
|
||||
public static function make(callable $get = null, callable $set = null): static
|
||||
{
|
||||
return new static($get, $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute accessor.
|
||||
*
|
||||
* @param callable $get
|
||||
* @return static
|
||||
*/
|
||||
public static function get(callable $get)
|
||||
{
|
||||
return new static($get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute mutator.
|
||||
*
|
||||
* @param callable $set
|
||||
* @return static
|
||||
*/
|
||||
public static function set(callable $set)
|
||||
{
|
||||
return new static(null, $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable object caching for the attribute.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function withoutObjectCaching()
|
||||
{
|
||||
$this->withObjectCaching = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable caching for the attribute.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function shouldCache()
|
||||
{
|
||||
$this->withCaching = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,29 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use LogicException;
|
||||
use Illuminate\Support\Arr;
|
||||
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 LogicException;
|
||||
|
||||
/**
|
||||
* @template TKey of array-key
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @extends \Illuminate\Support\Collection<TKey, TModel>
|
||||
*/
|
||||
class Collection extends BaseCollection implements QueueableCollection
|
||||
{
|
||||
/**
|
||||
* Find a model in the collection by key.
|
||||
*
|
||||
* @template TFindDefault
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $default
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
* @param TFindDefault $default
|
||||
* @return static<TKey|TModel>|TModel|TFindDefault
|
||||
*/
|
||||
public function find($key, $default = null)
|
||||
{
|
||||
@@ -22,6 +32,10 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
$key = $key->getKey();
|
||||
}
|
||||
|
||||
if ($key instanceof Arrayable) {
|
||||
$key = $key->toArray();
|
||||
}
|
||||
|
||||
if (is_array($key)) {
|
||||
if ($this->isEmpty()) {
|
||||
return new static;
|
||||
@@ -38,17 +52,17 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Load a set of relationships onto the collection.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function load($relations)
|
||||
{
|
||||
if (count($this->items) > 0) {
|
||||
if ($this->isNotEmpty()) {
|
||||
if (is_string($relations)) {
|
||||
$relations = func_get_args();
|
||||
}
|
||||
|
||||
$query = $this->first()->newQuery()->with($relations);
|
||||
$query = $this->first()->newQueryWithoutRelationships()->with($relations);
|
||||
|
||||
$this->items = $query->eagerLoadRelations($this->items);
|
||||
}
|
||||
@@ -57,14 +71,223 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the collection.
|
||||
* Load a set of aggregations over relationship's column onto the collection.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @param string $column
|
||||
* @param string|null $function
|
||||
* @return $this
|
||||
*/
|
||||
public function add($item)
|
||||
public function loadAggregate($relations, $column, $function = null)
|
||||
{
|
||||
$this->items[] = $item;
|
||||
if ($this->isEmpty()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$models = $this->first()->newModelQuery()
|
||||
->whereKey($this->modelKeys())
|
||||
->select($this->first()->getKeyName())
|
||||
->withAggregate($relations, $column, $function)
|
||||
->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)
|
||||
->mergeCasts($models->get($model->getKey())->getCasts());
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship counts onto the collection.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadCount($relations)
|
||||
{
|
||||
return $this->loadAggregate($relations, '*', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship's max column values onto the collection.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMax($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'max');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship's min column values onto the collection.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMin($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship's column summations onto the collection.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadSum($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'sum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship's average column values onto the collection.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadAvg($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'avg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of related existences onto the collection.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadExists($relations)
|
||||
{
|
||||
return $this->loadAggregate($relations, '*', 'exists');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationships onto the collection if they are not already eager loaded.
|
||||
*
|
||||
* @param array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|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)->whereNotNull();
|
||||
|
||||
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<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string> $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<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string> $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;
|
||||
}
|
||||
@@ -72,7 +295,7 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Determine if a key exists in the collection.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param (callable(TModel, TKey): bool)|TModel|string|int $key
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
@@ -97,7 +320,7 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Get the array of primary keys.
|
||||
*
|
||||
* @return array
|
||||
* @return array<int, array-key>
|
||||
*/
|
||||
public function modelKeys()
|
||||
{
|
||||
@@ -109,7 +332,7 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Merge the collection with the given items.
|
||||
*
|
||||
* @param \ArrayAccess|array $items
|
||||
* @param iterable<array-key, TModel> $items
|
||||
* @return static
|
||||
*/
|
||||
public function merge($items)
|
||||
@@ -126,8 +349,10 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Run a map over each of the items.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return \Illuminate\Support\Collection|static
|
||||
* @template TMapValue
|
||||
*
|
||||
* @param callable(TModel, TKey): TMapValue $callback
|
||||
* @return \Illuminate\Support\Collection<TKey, TMapValue>|static<TKey, TMapValue>
|
||||
*/
|
||||
public function map(callable $callback)
|
||||
{
|
||||
@@ -138,10 +363,30 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
}) ? $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.
|
||||
*
|
||||
* @template TMapWithKeysKey of array-key
|
||||
* @template TMapWithKeysValue
|
||||
*
|
||||
* @param callable(TModel, TKey): array<TMapWithKeysKey, TMapWithKeysValue> $callback
|
||||
* @return \Illuminate\Support\Collection<TMapWithKeysKey, TMapWithKeysValue>|static<TMapWithKeysKey, TMapWithKeysValue>
|
||||
*/
|
||||
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
|
||||
* @param array<array-key, string>|string $with
|
||||
* @return static
|
||||
*/
|
||||
public function fresh($with = [])
|
||||
@@ -158,15 +403,18 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
->get()
|
||||
->getDictionary();
|
||||
|
||||
return $this->map(function ($model) use ($freshModels) {
|
||||
return $model->exists ? $freshModels[$model->getKey()] : null;
|
||||
return $this->filter(function ($model) use ($freshModels) {
|
||||
return $model->exists && isset($freshModels[$model->getKey()]);
|
||||
})
|
||||
->map(function ($model) use ($freshModels) {
|
||||
return $freshModels[$model->getKey()];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff the collection with the given items.
|
||||
*
|
||||
* @param \ArrayAccess|array $items
|
||||
* @param iterable<array-key, TModel> $items
|
||||
* @return static
|
||||
*/
|
||||
public function diff($items)
|
||||
@@ -187,13 +435,17 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Intersect the collection with the given items.
|
||||
*
|
||||
* @param \ArrayAccess|array $items
|
||||
* @param iterable<array-key, TModel> $items
|
||||
* @return static
|
||||
*/
|
||||
public function intersect($items)
|
||||
{
|
||||
$intersect = new static;
|
||||
|
||||
if (empty($items)) {
|
||||
return $intersect;
|
||||
}
|
||||
|
||||
$dictionary = $this->getDictionary($items);
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
@@ -208,9 +460,9 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Return only unique items from the collection.
|
||||
*
|
||||
* @param string|callable|null $key
|
||||
* @param (callable(TModel, TKey): mixed)|string|null $key
|
||||
* @param bool $strict
|
||||
* @return static|\Illuminate\Support\Collection
|
||||
* @return static<int, TModel>
|
||||
*/
|
||||
public function unique($key = null, $strict = false)
|
||||
{
|
||||
@@ -224,8 +476,8 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Returns only the models from the collection with the specified keys.
|
||||
*
|
||||
* @param mixed $keys
|
||||
* @return static
|
||||
* @param array<array-key, mixed>|null $keys
|
||||
* @return static<int, TModel>
|
||||
*/
|
||||
public function only($keys)
|
||||
{
|
||||
@@ -241,8 +493,8 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Returns all models in the collection except the models with specified keys.
|
||||
*
|
||||
* @param mixed $keys
|
||||
* @return static
|
||||
* @param array<array-key, mixed>|null $keys
|
||||
* @return static<int, TModel>
|
||||
*/
|
||||
public function except($keys)
|
||||
{
|
||||
@@ -254,34 +506,41 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden across the entire collection.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @param array<array-key, string>|string $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHidden($attributes)
|
||||
{
|
||||
return $this->each(function ($model) use ($attributes) {
|
||||
$model->addHidden($attributes);
|
||||
});
|
||||
return $this->each->makeHidden($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible across the entire collection.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @param array<array-key, string>|string $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisible($attributes)
|
||||
{
|
||||
return $this->each(function ($model) use ($attributes) {
|
||||
$model->makeVisible($attributes);
|
||||
});
|
||||
return $this->each->makeVisible($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an attribute across the entire collection.
|
||||
*
|
||||
* @param array<array-key, string>|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
|
||||
* @param iterable<array-key, TModel>|null $items
|
||||
* @return array<array-key, TModel>
|
||||
*/
|
||||
public function getDictionary($items = null)
|
||||
{
|
||||
@@ -303,9 +562,9 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Get an array with the values of a given key.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string|array<array-key, string> $value
|
||||
* @param string|null $key
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection<int, mixed>
|
||||
*/
|
||||
public function pluck($value, $key = null)
|
||||
{
|
||||
@@ -315,7 +574,7 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Get the keys of the collection items.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection<int, TKey>
|
||||
*/
|
||||
public function keys()
|
||||
{
|
||||
@@ -325,18 +584,20 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Zip the collection together with one or more arrays.
|
||||
*
|
||||
* @param mixed ...$items
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @template TZipValue
|
||||
*
|
||||
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue> ...$items
|
||||
* @return \Illuminate\Support\Collection<int, \Illuminate\Support\Collection<int, TModel|TZipValue>>
|
||||
*/
|
||||
public function zip($items)
|
||||
{
|
||||
return call_user_func_array([$this->toBase(), 'zip'], func_get_args());
|
||||
return $this->toBase()->zip(...func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the collection of items into a single array.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection<int, mixed>
|
||||
*/
|
||||
public function collapse()
|
||||
{
|
||||
@@ -347,7 +608,7 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
* Get a flattened array of the items in the collection.
|
||||
*
|
||||
* @param int $depth
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection<int, mixed>
|
||||
*/
|
||||
public function flatten($depth = INF)
|
||||
{
|
||||
@@ -357,28 +618,57 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
/**
|
||||
* Flip the items in the collection.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @return \Illuminate\Support\Collection<TModel, TKey>
|
||||
*/
|
||||
public function flip()
|
||||
{
|
||||
return $this->toBase()->flip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad collection to the specified length with a value.
|
||||
*
|
||||
* @template TPadValue
|
||||
*
|
||||
* @param int $size
|
||||
* @param TPadValue $value
|
||||
* @return \Illuminate\Support\Collection<int, TModel|TPadValue>
|
||||
*/
|
||||
public function pad($size, $value)
|
||||
{
|
||||
return $this->toBase()->pad($size, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comparison function to detect duplicates.
|
||||
*
|
||||
* @param bool $strict
|
||||
* @return callable(TValue, TValue): bool
|
||||
*/
|
||||
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->count() === 0) {
|
||||
if ($this->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = get_class($this->first());
|
||||
$class = $this->getQueueableModelClass($this->first());
|
||||
|
||||
$this->each(function ($model) use ($class) {
|
||||
if (get_class($model) !== $class) {
|
||||
if ($this->getQueueableModelClass($model) !== $class) {
|
||||
throw new LogicException('Queueing collections with multiple model types is not supported.');
|
||||
}
|
||||
});
|
||||
@@ -386,13 +676,104 @@ class Collection extends BaseCollection implements QueueableCollection
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queueable class name for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return string
|
||||
*/
|
||||
protected function getQueueableModelClass($model)
|
||||
{
|
||||
return method_exists($model, 'getQueueableClassName')
|
||||
? $model->getQueueableClassName()
|
||||
: get_class($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifiers for all of the entities.
|
||||
*
|
||||
* @return array
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
public function getQueueableIds()
|
||||
{
|
||||
return $this->modelKeys();
|
||||
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<int, string>
|
||||
*/
|
||||
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(...array_values($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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,19 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait GuardsAttributes
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string>|bool
|
||||
*/
|
||||
protected $guarded = ['*'];
|
||||
|
||||
@@ -27,10 +25,17 @@ trait GuardsAttributes
|
||||
*/
|
||||
protected static $unguarded = false;
|
||||
|
||||
/**
|
||||
* The actual columns that exist on the database and can be guarded.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected static $guardableColumns = [];
|
||||
|
||||
/**
|
||||
* Get the fillable attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getFillable()
|
||||
{
|
||||
@@ -40,7 +45,7 @@ trait GuardsAttributes
|
||||
/**
|
||||
* Set the fillable attributes for the model.
|
||||
*
|
||||
* @param array $fillable
|
||||
* @param array<string> $fillable
|
||||
* @return $this
|
||||
*/
|
||||
public function fillable(array $fillable)
|
||||
@@ -50,20 +55,35 @@ trait GuardsAttributes
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new fillable attributes with existing fillable attributes on the model.
|
||||
*
|
||||
* @param array<string> $fillable
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeFillable(array $fillable)
|
||||
{
|
||||
$this->fillable = array_merge($this->fillable, $fillable);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the guarded attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getGuarded()
|
||||
{
|
||||
return $this->guarded;
|
||||
return $this->guarded === false
|
||||
? []
|
||||
: $this->guarded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the guarded attributes for the model.
|
||||
*
|
||||
* @param array $guarded
|
||||
* @param array<string> $guarded
|
||||
* @return $this
|
||||
*/
|
||||
public function guard(array $guarded)
|
||||
@@ -73,6 +93,19 @@ trait GuardsAttributes
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new guarded attributes with existing guarded attributes on the model.
|
||||
*
|
||||
* @param array<string> $guarded
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeGuarded(array $guarded)
|
||||
{
|
||||
$this->guarded = array_merge($this->guarded, $guarded);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all mass assignable restrictions.
|
||||
*
|
||||
@@ -95,7 +128,7 @@ trait GuardsAttributes
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if current state is "unguarded".
|
||||
* Determine if the current state is "unguarded".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -152,7 +185,8 @@ trait GuardsAttributes
|
||||
}
|
||||
|
||||
return empty($this->getFillable()) &&
|
||||
! Str::startsWith($key, '_');
|
||||
! str_contains($key, '.') &&
|
||||
! str_starts_with($key, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +197,35 @@ trait GuardsAttributes
|
||||
*/
|
||||
public function isGuarded($key)
|
||||
{
|
||||
return in_array($key, $this->getGuarded()) || $this->getGuarded() == ['*'];
|
||||
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)])) {
|
||||
$columns = $this->getConnection()
|
||||
->getSchemaBuilder()
|
||||
->getColumnListing($this->getTable());
|
||||
|
||||
if (empty($columns)) {
|
||||
return true;
|
||||
}
|
||||
static::$guardableColumns[get_class($this)] = $columns;
|
||||
}
|
||||
|
||||
return in_array($key, static::$guardableColumns[get_class($this)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +235,7 @@ trait GuardsAttributes
|
||||
*/
|
||||
public function totallyGuarded()
|
||||
{
|
||||
return count($this->getFillable()) == 0 && $this->getGuarded() == ['*'];
|
||||
return count($this->getFillable()) === 0 && $this->getGuarded() == ['*'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,9 @@
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Events\NullDispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait HasEvents
|
||||
{
|
||||
@@ -13,7 +16,7 @@ trait HasEvents
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $events = [];
|
||||
protected $dispatchesEvents = [];
|
||||
|
||||
/**
|
||||
* User exposed observable events.
|
||||
@@ -25,27 +28,65 @@ trait HasEvents
|
||||
protected $observables = [];
|
||||
|
||||
/**
|
||||
* Register an observer with the Model.
|
||||
* Register observers with the model.
|
||||
*
|
||||
* @param object|string $class
|
||||
* @param object|array|string $classes
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function observe($class)
|
||||
public static function observe($classes)
|
||||
{
|
||||
$instance = new static;
|
||||
|
||||
$className = is_string($class) ? $class : get_class($class);
|
||||
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 ($instance->getObservableEvents() as $event) {
|
||||
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.
|
||||
*
|
||||
@@ -55,9 +96,9 @@ trait HasEvents
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
'creating', 'created', 'updating', 'updated',
|
||||
'deleting', 'deleted', 'saving', 'saved',
|
||||
'restoring', 'restored',
|
||||
'retrieved', 'creating', 'created', 'updating', 'updated',
|
||||
'saving', 'saved', 'restoring', 'restored', 'replicating',
|
||||
'deleting', 'deleted', 'forceDeleted',
|
||||
],
|
||||
$this->observables
|
||||
);
|
||||
@@ -106,7 +147,7 @@ trait HasEvents
|
||||
* Register a model event with the dispatcher.
|
||||
*
|
||||
* @param string $event
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
protected static function registerModelEvent($event, $callback)
|
||||
@@ -134,7 +175,7 @@ trait HasEvents
|
||||
// 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' : 'fire';
|
||||
$method = $halt ? 'until' : 'dispatch';
|
||||
|
||||
$result = $this->filterModelEventResults(
|
||||
$this->fireCustomModelEvent($event, $method)
|
||||
@@ -158,11 +199,11 @@ trait HasEvents
|
||||
*/
|
||||
protected function fireCustomModelEvent($event, $method)
|
||||
{
|
||||
if (! isset($this->events[$event])) {
|
||||
if (! isset($this->dispatchesEvents[$event])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = static::$dispatcher->$method(new $this->events[$event]($this));
|
||||
$result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));
|
||||
|
||||
if (! is_null($result)) {
|
||||
return $result;
|
||||
@@ -186,10 +227,21 @@ trait HasEvents
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a retrieved model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function retrieved($callback)
|
||||
{
|
||||
static::registerModelEvent('retrieved', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a saving model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function saving($callback)
|
||||
@@ -200,7 +252,7 @@ trait HasEvents
|
||||
/**
|
||||
* Register a saved model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function saved($callback)
|
||||
@@ -211,7 +263,7 @@ trait HasEvents
|
||||
/**
|
||||
* Register an updating model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function updating($callback)
|
||||
@@ -222,7 +274,7 @@ trait HasEvents
|
||||
/**
|
||||
* Register an updated model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function updated($callback)
|
||||
@@ -233,7 +285,7 @@ trait HasEvents
|
||||
/**
|
||||
* Register a creating model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function creating($callback)
|
||||
@@ -244,7 +296,7 @@ trait HasEvents
|
||||
/**
|
||||
* Register a created model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function created($callback)
|
||||
@@ -252,10 +304,21 @@ trait HasEvents
|
||||
static::registerModelEvent('created', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a replicating model event with the dispatcher.
|
||||
*
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function replicating($callback)
|
||||
{
|
||||
static::registerModelEvent('replicating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a deleting model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function deleting($callback)
|
||||
@@ -266,7 +329,7 @@ trait HasEvents
|
||||
/**
|
||||
* Register a deleted model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function deleted($callback)
|
||||
@@ -275,7 +338,7 @@ trait HasEvents
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all of the event listeners for the model.
|
||||
* Remove all the event listeners for the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -290,6 +353,10 @@ trait HasEvents
|
||||
foreach ($instance->getObservableEvents() as $event) {
|
||||
static::$dispatcher->forget("eloquent.{$event}: ".static::class);
|
||||
}
|
||||
|
||||
foreach (array_values($instance->dispatchesEvents) as $event) {
|
||||
static::$dispatcher->forget($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,4 +389,27 @@ trait HasEvents
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
trait HasGlobalScopes
|
||||
{
|
||||
@@ -13,14 +13,14 @@ trait HasGlobalScopes
|
||||
* Register a new global scope on the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|\Closure|string $scope
|
||||
* @param \Closure|null $implementation
|
||||
* @param \Illuminate\Database\Eloquent\Scope|\Closure|null $implementation
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function addGlobalScope($scope, Closure $implementation = null)
|
||||
public static function addGlobalScope($scope, $implementation = null)
|
||||
{
|
||||
if (is_string($scope) && ! is_null($implementation)) {
|
||||
if (is_string($scope) && ($implementation instanceof Closure || $implementation instanceof Scope)) {
|
||||
return static::$globalScopes[static::class][$scope] = $implementation;
|
||||
} elseif ($scope instanceof Closure) {
|
||||
return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope;
|
||||
|
||||
@@ -2,20 +2,25 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\ClassMorphViolationException;
|
||||
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\Pivot;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
|
||||
trait HasRelationships
|
||||
{
|
||||
@@ -36,19 +41,60 @@ trait HasRelationships
|
||||
/**
|
||||
* The many to many relationship methods.
|
||||
*
|
||||
* @var array
|
||||
* @var string[]
|
||||
*/
|
||||
public static $manyMethods = [
|
||||
'belongsToMany', 'morphToMany', 'morphedByMany',
|
||||
'guessBelongsToManyRelation', 'findFirstMethodThatIsntRelation',
|
||||
];
|
||||
|
||||
/**
|
||||
* The relation resolver callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $relationResolvers = [];
|
||||
|
||||
/**
|
||||
* Get the dynamic relation resolver if defined or inherited, or return null.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function relationResolver($class, $key)
|
||||
{
|
||||
if ($resolver = static::$relationResolvers[$class][$key] ?? null) {
|
||||
return $resolver;
|
||||
}
|
||||
|
||||
if ($parent = get_parent_class($class)) {
|
||||
return $this->relationResolver($parent, $key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a dynamic relation resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function resolveRelationUsing($name, Closure $callback)
|
||||
{
|
||||
static::$relationResolvers = array_replace_recursive(
|
||||
static::$relationResolvers,
|
||||
[static::class => [$name => $callback]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a one-to-one relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function hasOne($related, $foreignKey = null, $localKey = null)
|
||||
@@ -59,7 +105,64 @@ trait HasRelationships
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
|
||||
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 = $this->newRelatedThroughInstance($through);
|
||||
|
||||
$firstKey = $firstKey ?: $this->getForeignKey();
|
||||
|
||||
$secondKey = $secondKey ?: $through->getForeignKey();
|
||||
|
||||
return $this->newHasOneThrough(
|
||||
$this->newRelatedInstance($related)->newQuery(), $this, $through,
|
||||
$firstKey, $secondKey, $localKey ?: $this->getKeyName(),
|
||||
$secondLocalKey ?: $through->getKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasOneThrough relationship.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,31 +170,46 @@ trait HasRelationships
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @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);
|
||||
|
||||
list($type, $id) = $this->getMorphs($name, $type, $id);
|
||||
[$type, $id] = $this->getMorphs($name, $type, $id);
|
||||
|
||||
$table = $instance->getTable();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
|
||||
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 $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $relation
|
||||
* @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)
|
||||
@@ -112,41 +230,57 @@ trait HasRelationships
|
||||
$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.
|
||||
// Once we have the foreign key names we'll just create a new Eloquent query
|
||||
// for the related models and return the relationship instance which will
|
||||
// actually be responsible for retrieving and hydrating every relation.
|
||||
$ownerKey = $ownerKey ?: $instance->getKeyName();
|
||||
|
||||
return new BelongsTo(
|
||||
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 $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @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)
|
||||
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();
|
||||
|
||||
list($type, $id) = $this->getMorphs(
|
||||
[$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 empty($class = $this->{$type})
|
||||
? $this->morphEagerTo($name, $type, $id)
|
||||
: $this->morphInstanceTo($class, $name, $type, $id);
|
||||
return is_null($class = $this->getAttributeFromArray($type)) || $class === ''
|
||||
? $this->morphEagerTo($name, $type, $id, $ownerKey)
|
||||
: $this->morphInstanceTo($class, $name, $type, $id, $ownerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,12 +289,13 @@ trait HasRelationships
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
protected function morphEagerTo($name, $type, $id)
|
||||
protected function morphEagerTo($name, $type, $id, $ownerKey)
|
||||
{
|
||||
return new MorphTo(
|
||||
$this->newQuery()->setEagerLoads([]), $this, $id, null, $type, $name
|
||||
return $this->newMorphTo(
|
||||
$this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name
|
||||
);
|
||||
}
|
||||
|
||||
@@ -171,19 +306,36 @@ trait HasRelationships
|
||||
* @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)
|
||||
protected function morphInstanceTo($target, $name, $type, $id, $ownerKey)
|
||||
{
|
||||
$instance = $this->newRelatedInstance(
|
||||
static::getActualClassNameForMorph($target)
|
||||
);
|
||||
|
||||
return new MorphTo(
|
||||
$instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
|
||||
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.
|
||||
*
|
||||
@@ -202,7 +354,7 @@ trait HasRelationships
|
||||
*/
|
||||
protected function guessBelongsToRelation()
|
||||
{
|
||||
list($one, $two, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
[$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
|
||||
return $caller['function'];
|
||||
}
|
||||
@@ -211,8 +363,8 @@ trait HasRelationships
|
||||
* Define a one-to-many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function hasMany($related, $foreignKey = null, $localKey = null)
|
||||
@@ -223,11 +375,25 @@ trait HasRelationships
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return new HasMany(
|
||||
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.
|
||||
*
|
||||
@@ -236,21 +402,43 @@ trait HasRelationships
|
||||
* @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)
|
||||
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
|
||||
{
|
||||
$through = new $through;
|
||||
$through = $this->newRelatedThroughInstance($through);
|
||||
|
||||
$firstKey = $firstKey ?: $this->getForeignKey();
|
||||
|
||||
$secondKey = $secondKey ?: $through->getForeignKey();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
return $this->newHasManyThrough(
|
||||
$this->newRelatedInstance($related)->newQuery(),
|
||||
$this,
|
||||
$through,
|
||||
$firstKey,
|
||||
$secondKey,
|
||||
$localKey ?: $this->getKeyName(),
|
||||
$secondLocalKey ?: $through->getKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
return new HasManyThrough($instance->newQuery(), $this, $through, $firstKey, $secondKey, $localKey);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,9 +446,9 @@ trait HasRelationships
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @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)
|
||||
@@ -270,26 +458,44 @@ trait HasRelationships
|
||||
// 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.
|
||||
list($type, $id) = $this->getMorphs($name, $type, $id);
|
||||
[$type, $id] = $this->getMorphs($name, $type, $id);
|
||||
|
||||
$table = $instance->getTable();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
|
||||
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 $table
|
||||
* @param string $foreignKey
|
||||
* @param string $relatedKey
|
||||
* @param string $relation
|
||||
* @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, $foreignKey = null, $relatedKey = null, $relation = null)
|
||||
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
|
||||
@@ -303,34 +509,59 @@ trait HasRelationships
|
||||
// instances as well as the relationship instances we need for this.
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey();
|
||||
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
|
||||
|
||||
$relatedKey = $relatedKey ?: $instance->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);
|
||||
$table = $this->joiningTable($related, $instance);
|
||||
}
|
||||
|
||||
return new BelongsToMany(
|
||||
$instance->newQuery(), $this, $table, $foreignKey, $relatedKey, $relation
|
||||
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 $table
|
||||
* @param string $foreignKey
|
||||
* @param string $relatedKey
|
||||
* @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, $foreignKey = null, $relatedKey = null, $inverse = false)
|
||||
public function morphToMany($related, $name, $table = null, $foreignPivotKey = null,
|
||||
$relatedPivotKey = null, $parentKey = null,
|
||||
$relatedKey = null, $inverse = false)
|
||||
{
|
||||
$caller = $this->guessBelongsToManyRelation();
|
||||
|
||||
@@ -339,52 +570,91 @@ trait HasRelationships
|
||||
// instances, as well as the relationship instances we need for these.
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignKey = $foreignKey ?: $name.'_id';
|
||||
$foreignPivotKey = $foreignPivotKey ?: $name.'_id';
|
||||
|
||||
$relatedKey = $relatedKey ?: $instance->getForeignKey();
|
||||
$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.
|
||||
$table = $table ?: Str::plural($name);
|
||||
// Now we're ready to create a new query builder for the related model and
|
||||
// the relationship instances for this relation. This relation will set
|
||||
// appropriate query constraints then entirely manage the hydrations.
|
||||
if (! $table) {
|
||||
$words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
return new MorphToMany(
|
||||
$lastWord = array_pop($words);
|
||||
|
||||
$table = implode('', $words).Str::plural($lastWord);
|
||||
}
|
||||
|
||||
return $this->newMorphToMany(
|
||||
$instance->newQuery(), $this, $name, $table,
|
||||
$foreignKey, $relatedKey, $caller, $inverse
|
||||
$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 $table
|
||||
* @param string $foreignKey
|
||||
* @param string $relatedKey
|
||||
* @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, $foreignKey = null, $relatedKey = null)
|
||||
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
|
||||
$relatedPivotKey = null, $parentKey = null, $relatedKey = null)
|
||||
{
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey();
|
||||
$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.
|
||||
$relatedKey = $relatedKey ?: $name.'_id';
|
||||
$relatedPivotKey = $relatedPivotKey ?: $name.'_id';
|
||||
|
||||
return $this->morphToMany($related, $name, $table, $foreignKey, $relatedKey, true);
|
||||
return $this->morphToMany(
|
||||
$related, $name, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey, true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationship name of the belongs to many.
|
||||
* Get the relationship name of the belongsToMany relationship.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
protected function guessBelongsToManyRelation()
|
||||
{
|
||||
$caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
|
||||
return ! in_array($trace['function'], Model::$manyMethods);
|
||||
return ! in_array(
|
||||
$trace['function'],
|
||||
array_merge(static::$manyMethods, ['guessBelongsToManyRelation'])
|
||||
);
|
||||
});
|
||||
|
||||
return ! is_null($caller) ? $caller['function'] : null;
|
||||
@@ -394,24 +664,36 @@ trait HasRelationships
|
||||
* 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)
|
||||
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.
|
||||
$models = [
|
||||
Str::snake(class_basename($related)),
|
||||
Str::snake(class_basename($this)),
|
||||
$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($models);
|
||||
sort($segments);
|
||||
|
||||
return strtolower(implode('_', $models));
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,7 +704,7 @@ trait HasRelationships
|
||||
*/
|
||||
public function touches($relation)
|
||||
{
|
||||
return in_array($relation, $this->touches);
|
||||
return in_array($relation, $this->getTouchedRelations());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,7 +714,7 @@ trait HasRelationships
|
||||
*/
|
||||
public function touchOwners()
|
||||
{
|
||||
foreach ($this->touches as $relation) {
|
||||
foreach ($this->getTouchedRelations() as $relation) {
|
||||
$this->$relation()->touch();
|
||||
|
||||
if ($this->$relation instanceof self) {
|
||||
@@ -440,9 +722,7 @@ trait HasRelationships
|
||||
|
||||
$this->$relation->touchOwners();
|
||||
} elseif ($this->$relation instanceof Collection) {
|
||||
$this->$relation->each(function (Model $relation) {
|
||||
$relation->touchOwners();
|
||||
});
|
||||
$this->$relation->each->touchOwners();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,6 +753,14 @@ trait HasRelationships
|
||||
return array_search(static::class, $morphMap, true);
|
||||
}
|
||||
|
||||
if (static::class === Pivot::class) {
|
||||
return static::class;
|
||||
}
|
||||
|
||||
if (Relation::requiresMorphMap()) {
|
||||
throw new ClassMorphViolationException($this);
|
||||
}
|
||||
|
||||
return static::class;
|
||||
}
|
||||
|
||||
@@ -491,6 +779,17 @@ trait HasRelationships
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance for a related "through" model.
|
||||
*
|
||||
* @param string $class
|
||||
* @return mixed
|
||||
*/
|
||||
protected function newRelatedThroughInstance($class)
|
||||
{
|
||||
return new $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the loaded relations for the instance.
|
||||
*
|
||||
@@ -524,7 +823,7 @@ trait HasRelationships
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific relationship in the model.
|
||||
* Set the given relationship on the model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param mixed $value
|
||||
@@ -537,6 +836,19 @@ trait HasRelationships
|
||||
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.
|
||||
*
|
||||
@@ -550,6 +862,30 @@ trait HasRelationships
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
trait HasTimestamps
|
||||
{
|
||||
@@ -13,13 +13,27 @@ trait HasTimestamps
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* The list of models classes that have timestamps temporarily disabled.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $ignoreTimestampsOn = [];
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp.
|
||||
*
|
||||
* @param string|null $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function touch()
|
||||
public function touch($attribute = null)
|
||||
{
|
||||
if ($attribute) {
|
||||
$this->$attribute = $this->freshTimestamp();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
if (! $this->usesTimestamps()) {
|
||||
return false;
|
||||
}
|
||||
@@ -29,22 +43,39 @@ trait HasTimestamps
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp without raising any events.
|
||||
*
|
||||
* @param string|null $attribute
|
||||
* @return bool
|
||||
*/
|
||||
public function touchQuietly($attribute = null)
|
||||
{
|
||||
return static::withoutEvents(fn () => $this->touch($attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the creation and update timestamps.
|
||||
*
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
protected function updateTimestamps()
|
||||
public function updateTimestamps()
|
||||
{
|
||||
$time = $this->freshTimestamp();
|
||||
|
||||
if (! $this->isDirty(static::UPDATED_AT)) {
|
||||
$updatedAtColumn = $this->getUpdatedAtColumn();
|
||||
|
||||
if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) {
|
||||
$this->setUpdatedAt($time);
|
||||
}
|
||||
|
||||
if (! $this->exists && ! $this->isDirty(static::CREATED_AT)) {
|
||||
$createdAtColumn = $this->getCreatedAtColumn();
|
||||
|
||||
if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) {
|
||||
$this->setCreatedAt($time);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +86,7 @@ trait HasTimestamps
|
||||
*/
|
||||
public function setCreatedAt($value)
|
||||
{
|
||||
$this->{static::CREATED_AT} = $value;
|
||||
$this->{$this->getCreatedAtColumn()} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -68,7 +99,7 @@ trait HasTimestamps
|
||||
*/
|
||||
public function setUpdatedAt($value)
|
||||
{
|
||||
$this->{static::UPDATED_AT} = $value;
|
||||
$this->{$this->getUpdatedAtColumn()} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -76,11 +107,11 @@ trait HasTimestamps
|
||||
/**
|
||||
* Get a fresh timestamp for the model.
|
||||
*
|
||||
* @return \Carbon\Carbon
|
||||
* @return \Illuminate\Support\Carbon
|
||||
*/
|
||||
public function freshTimestamp()
|
||||
{
|
||||
return new Carbon;
|
||||
return Date::now();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,13 +131,13 @@ trait HasTimestamps
|
||||
*/
|
||||
public function usesTimestamps()
|
||||
{
|
||||
return $this->timestamps;
|
||||
return $this->timestamps && ! static::isIgnoringTimestamps($this::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "created at" column.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCreatedAtColumn()
|
||||
{
|
||||
@@ -116,10 +147,78 @@ trait HasTimestamps
|
||||
/**
|
||||
* Get the name of the "updated at" column.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUpdatedAtColumn()
|
||||
{
|
||||
return static::UPDATED_AT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "created at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedCreatedAtColumn()
|
||||
{
|
||||
return $this->qualifyColumn($this->getCreatedAtColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "updated at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedUpdatedAtColumn()
|
||||
{
|
||||
return $this->qualifyColumn($this->getUpdatedAtColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable timestamps for the current class during the given callback scope.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutTimestamps(callable $callback)
|
||||
{
|
||||
return static::withoutTimestampsOn([static::class], $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable timestamps for the given model classes during the given callback scope.
|
||||
*
|
||||
* @param array $models
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutTimestampsOn($models, $callback)
|
||||
{
|
||||
static::$ignoreTimestampsOn = array_values(array_merge(static::$ignoreTimestampsOn, $models));
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::$ignoreTimestampsOn = array_values(array_diff(static::$ignoreTimestampsOn, $models));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given model is ignoring timestamps / touches.
|
||||
*
|
||||
* @param string|null $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function isIgnoringTimestamps($class = null)
|
||||
{
|
||||
$class ??= static::class;
|
||||
|
||||
foreach (static::$ignoreTimestampsOn as $ignoredClass) {
|
||||
if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
96
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php
vendored
Normal file
96
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasUlids.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasUlids
|
||||
{
|
||||
/**
|
||||
* Boot the trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootHasUlids()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
foreach ($model->uniqueIds() as $column) {
|
||||
if (empty($model->{$column})) {
|
||||
$model->{$column} = $model->newUniqueId();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new ULID for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return strtolower((string) Str::ulid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model for a bound value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function resolveRouteBindingQuery($query, $value, $field = null)
|
||||
{
|
||||
if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUlid($value)) {
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
|
||||
}
|
||||
|
||||
if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUlid($value)) {
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
|
||||
}
|
||||
|
||||
return parent::resolveRouteBindingQuery($query, $value, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should receive a unique identifier.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function uniqueIds()
|
||||
{
|
||||
return [$this->getKeyName()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-incrementing key type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyType()
|
||||
{
|
||||
if (in_array($this->getKeyName(), $this->uniqueIds())) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
return $this->keyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value indicating whether the IDs are incrementing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIncrementing()
|
||||
{
|
||||
if (in_array($this->getKeyName(), $this->uniqueIds())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->incrementing;
|
||||
}
|
||||
}
|
||||
96
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php
vendored
Normal file
96
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasUuids.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasUuids
|
||||
{
|
||||
/**
|
||||
* Generate a primary UUID for the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootHasUuids()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
foreach ($model->uniqueIds() as $column) {
|
||||
if (empty($model->{$column})) {
|
||||
$model->{$column} = $model->newUniqueId();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new UUID for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function newUniqueId()
|
||||
{
|
||||
return (string) Str::orderedUuid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should receive a unique identifier.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function uniqueIds()
|
||||
{
|
||||
return [$this->getKeyName()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model for a bound value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function resolveRouteBindingQuery($query, $value, $field = null)
|
||||
{
|
||||
if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUuid($value)) {
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
|
||||
}
|
||||
|
||||
if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUuid($value)) {
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
|
||||
}
|
||||
|
||||
return parent::resolveRouteBindingQuery($query, $value, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-incrementing key type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyType()
|
||||
{
|
||||
if (in_array($this->getKeyName(), $this->uniqueIds())) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
return $this->keyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value indicating whether the IDs are incrementing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIncrementing()
|
||||
{
|
||||
if (in_array($this->getKeyName(), $this->uniqueIds())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->incrementing;
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,21 @@ trait HidesAttributes
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be visible in serialization.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $visible = [];
|
||||
|
||||
/**
|
||||
* Get the hidden attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getHidden()
|
||||
{
|
||||
@@ -31,7 +31,7 @@ trait HidesAttributes
|
||||
/**
|
||||
* Set the hidden attributes for the model.
|
||||
*
|
||||
* @param array $hidden
|
||||
* @param array<string> $hidden
|
||||
* @return $this
|
||||
*/
|
||||
public function setHidden(array $hidden)
|
||||
@@ -41,23 +41,10 @@ trait HidesAttributes
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hidden attributes for the model.
|
||||
*
|
||||
* @param array|string|null $attributes
|
||||
* @return void
|
||||
*/
|
||||
public function addHidden($attributes = null)
|
||||
{
|
||||
$this->hidden = array_merge(
|
||||
$this->hidden, is_array($attributes) ? $attributes : func_get_args()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visible attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getVisible()
|
||||
{
|
||||
@@ -67,7 +54,7 @@ trait HidesAttributes
|
||||
/**
|
||||
* Set the visible attributes for the model.
|
||||
*
|
||||
* @param array $visible
|
||||
* @param array<string> $visible
|
||||
* @return $this
|
||||
*/
|
||||
public function setVisible(array $visible)
|
||||
@@ -77,50 +64,61 @@ trait HidesAttributes
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add visible attributes for the model.
|
||||
*
|
||||
* @param array|string|null $attributes
|
||||
* @return void
|
||||
*/
|
||||
public function addVisible($attributes = null)
|
||||
{
|
||||
$this->visible = array_merge(
|
||||
$this->visible, is_array($attributes) ? $attributes : func_get_args()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @param array<string>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisible($attributes)
|
||||
{
|
||||
$this->hidden = array_diff($this->hidden, (array) $attributes);
|
||||
$attributes = is_array($attributes) ? $attributes : func_get_args();
|
||||
|
||||
$this->hidden = array_diff($this->hidden, $attributes);
|
||||
|
||||
if (! empty($this->visible)) {
|
||||
$this->addVisible($attributes);
|
||||
$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>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisibleIf($condition, $attributes)
|
||||
{
|
||||
return value($condition, $this) ? $this->makeVisible($attributes) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @param array<string>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHidden($attributes)
|
||||
{
|
||||
$attributes = (array) $attributes;
|
||||
|
||||
$this->visible = array_diff($this->visible, $attributes);
|
||||
|
||||
$this->hidden = array_unique(array_merge($this->hidden, $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>|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHiddenIf($condition, $attributes)
|
||||
{
|
||||
return value($condition, $this) ? $this->makeHidden($attributes) : $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,46 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Closure;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\RelationNotFoundException;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait QueriesRelationships
|
||||
{
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation|string $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @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 (strpos($relation, '.') !== false) {
|
||||
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
|
||||
if (is_string($relation)) {
|
||||
if (str_contains($relation, '.')) {
|
||||
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
|
||||
}
|
||||
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
if ($relation instanceof MorphTo) {
|
||||
return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback);
|
||||
}
|
||||
|
||||
// If we only need to check for the existence of the relation, then we can optimize
|
||||
// the subquery to only run a "where exists" clause instead of this full "count"
|
||||
@@ -38,7 +51,7 @@ trait QueriesRelationships
|
||||
: 'getRelationExistenceCountQuery';
|
||||
|
||||
$hasQuery = $relation->{$method}(
|
||||
$relation->getRelated()->newQuery(), $this
|
||||
$relation->getRelated()->newQueryWithoutRelationships(), $this
|
||||
);
|
||||
|
||||
// Next we will call any given callback as an "anonymous" scope so they can get the
|
||||
@@ -60,7 +73,7 @@ trait QueriesRelationships
|
||||
*
|
||||
* @param string $relations
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
@@ -69,7 +82,14 @@ trait QueriesRelationships
|
||||
{
|
||||
$relations = explode('.', $relations);
|
||||
|
||||
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $boolean, $callback) {
|
||||
$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.
|
||||
@@ -78,7 +98,7 @@ trait QueriesRelationships
|
||||
: $q->has(array_shift($relations), $operator, $count, 'and', $callback);
|
||||
};
|
||||
|
||||
return $this->has(array_shift($relations), '>=', 1, $boolean, $closure);
|
||||
return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +106,7 @@ trait QueriesRelationships
|
||||
*
|
||||
* @param string $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orHas($relation, $operator = '>=', $count = 1)
|
||||
@@ -107,13 +127,24 @@ trait QueriesRelationships
|
||||
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
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
|
||||
@@ -121,13 +152,30 @@ trait QueriesRelationships
|
||||
return $this->has($relation, $operator, $count, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* Also load the relationship with same condition.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function withWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->whereHas(Str::before($relation, ':'), $callback, $operator, $count)
|
||||
->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @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)
|
||||
@@ -148,12 +196,406 @@ trait QueriesRelationships
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to count the relations.
|
||||
* 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 \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
$types = (array) $types;
|
||||
|
||||
if ($types === ['*']) {
|
||||
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->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->qualifyColumn($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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 \Illuminate\Database\Eloquent\Relations\MorphTo|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 a basic where clause to a relationship query.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereHas($relation, function ($query) use ($column, $operator, $value) {
|
||||
if ($column instanceof Closure) {
|
||||
$column($query);
|
||||
} else {
|
||||
$query->where($column, $operator, $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "or where" clause to a relationship query.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) {
|
||||
if ($column instanceof Closure) {
|
||||
$column($query);
|
||||
} else {
|
||||
$query->where($column, $operator, $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with a where clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a morph-to relationship condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereMorphedTo($relation, $model, $boolean = 'and')
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array($model, $morphMap)) {
|
||||
$model = array_search($model, $morphMap, true);
|
||||
}
|
||||
|
||||
return $this->where($relation->getMorphType(), $model, null, $boolean);
|
||||
}
|
||||
|
||||
return $this->where(function ($query) use ($relation, $model) {
|
||||
$query->where($relation->getMorphType(), $model->getMorphClass())
|
||||
->where($relation->getForeignKeyName(), $model->getKey());
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a not morph-to relationship condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereNotMorphedTo($relation, $model, $boolean = 'and')
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array($model, $morphMap)) {
|
||||
$model = array_search($model, $morphMap, true);
|
||||
}
|
||||
|
||||
return $this->whereNot($relation->getMorphType(), '<=>', $model, $boolean);
|
||||
}
|
||||
|
||||
return $this->whereNot(function ($query) use ($relation, $model) {
|
||||
$query->where($relation->getMorphType(), '<=>', $model->getMorphClass())
|
||||
->where($relation->getForeignKeyName(), '<=>', $model->getKey());
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a morph-to relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereMorphedTo($relation, $model)
|
||||
{
|
||||
return $this->whereMorphedTo($relation, $model, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a not morph-to relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereNotMorphedTo($relation, $model)
|
||||
{
|
||||
return $this->whereNotMorphedTo($relation, $model, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "belongs to" relationship where clause to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model> $related
|
||||
* @param string|null $relationshipName
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\RelationNotFoundException
|
||||
*/
|
||||
public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and')
|
||||
{
|
||||
if (! $related instanceof Collection) {
|
||||
$relatedCollection = $related->newCollection([$related]);
|
||||
} else {
|
||||
$relatedCollection = $related;
|
||||
|
||||
$related = $relatedCollection->first();
|
||||
}
|
||||
|
||||
if ($relatedCollection->isEmpty()) {
|
||||
throw new InvalidArgumentException('Collection given to whereBelongsTo method may not be empty.');
|
||||
}
|
||||
|
||||
if ($relationshipName === null) {
|
||||
$relationshipName = Str::camel(class_basename($related));
|
||||
}
|
||||
|
||||
try {
|
||||
$relationship = $this->model->{$relationshipName}();
|
||||
} catch (BadMethodCallException $exception) {
|
||||
throw RelationNotFoundException::make($this->model, $relationshipName);
|
||||
}
|
||||
|
||||
if (! $relationship instanceof BelongsTo) {
|
||||
throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class);
|
||||
}
|
||||
|
||||
$this->whereIn(
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
$relatedCollection->pluck($relationship->getOwnerKeyName())->toArray(),
|
||||
$boolean,
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "BelongsTo" relationship with an "or where" clause to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $related
|
||||
* @param string|null $relationshipName
|
||||
* @return $this
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function orWhereBelongsTo($related, $relationshipName = null)
|
||||
{
|
||||
return $this->whereBelongsTo($related, $relationshipName, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include an aggregate value for a relationship.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @param string $column
|
||||
* @param string $function
|
||||
* @return $this
|
||||
*/
|
||||
public function withCount($relations)
|
||||
public function withAggregate($relations, $column, $function = null)
|
||||
{
|
||||
if (empty($relations)) {
|
||||
return $this;
|
||||
@@ -163,44 +605,167 @@ trait QueriesRelationships
|
||||
$this->query->select([$this->query->from.'.*']);
|
||||
}
|
||||
|
||||
$relations = is_array($relations) ? $relations : func_get_args();
|
||||
$relations = is_array($relations) ? $relations : [$relations];
|
||||
|
||||
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
|
||||
// First we will determine if the name has been aliased using an "as" clause on the name
|
||||
// and if it has we will extract the actual relationship name and the desired name of
|
||||
// the resulting column. This allows multiple counts on the same relationship name.
|
||||
// the resulting column. This allows multiple aggregates on the same relationships.
|
||||
$segments = explode(' ', $name);
|
||||
|
||||
unset($alias);
|
||||
|
||||
if (count($segments) == 3 && Str::lower($segments[1]) == 'as') {
|
||||
list($name, $alias) = [$segments[0], $segments[2]];
|
||||
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
|
||||
if ($function) {
|
||||
$hashedColumn = $this->getRelationHashedColumn($column, $relation);
|
||||
|
||||
$wrappedColumn = $this->getQuery()->getGrammar()->wrap(
|
||||
$column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
|
||||
);
|
||||
|
||||
$expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn);
|
||||
} else {
|
||||
$expression = $column;
|
||||
}
|
||||
|
||||
// Here, we will grab the relationship sub-query and prepare to add it to the main query
|
||||
// as a sub-select. First, we'll get the "has" query and use that to get the relation
|
||||
// count query. We will normalize the relation name then append _count as the name.
|
||||
$query = $relation->getRelationExistenceCountQuery(
|
||||
$relation->getRelated()->newQuery(), $this
|
||||
);
|
||||
// sub-query. We'll format this relationship name and append this column if needed.
|
||||
$query = $relation->getRelationExistenceQuery(
|
||||
$relation->getRelated()->newQuery(), $this, new Expression($expression)
|
||||
)->setBindings([], 'select');
|
||||
|
||||
$query->callScope($constraints);
|
||||
|
||||
$query->mergeConstraintsFrom($relation->getQuery());
|
||||
$query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
|
||||
|
||||
// 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 = snake_case(isset($alias) ? $alias : $name).'_count';
|
||||
// If the query contains certain elements like orderings / more than one column selected
|
||||
// then we will remove those elements from the query so that it will execute properly
|
||||
// when given to the database. Otherwise, we may receive SQL errors or poor syntax.
|
||||
$query->orders = null;
|
||||
$query->setBindings([], 'order');
|
||||
|
||||
$this->selectSub($query->toBase(), $column);
|
||||
if (count($query->columns) > 1) {
|
||||
$query->columns = [$query->columns[0]];
|
||||
$query->bindings['select'] = [];
|
||||
}
|
||||
|
||||
// Finally, we will make the proper column alias to the query and run this sub-select on
|
||||
// the query builder. Then, we will return the builder instance back to the developer
|
||||
// for further constraint chaining that needs to take place on the query as needed.
|
||||
$alias ??= Str::snake(
|
||||
preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column")
|
||||
);
|
||||
|
||||
if ($function === 'exists') {
|
||||
$this->selectRaw(
|
||||
sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)),
|
||||
$query->getBindings()
|
||||
)->withCasts([$alias => 'bool']);
|
||||
} else {
|
||||
$this->selectSub(
|
||||
$function ? $query : $query->limit(1),
|
||||
$alias
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relation hashed column name for the given column and relation.
|
||||
*
|
||||
* @param string $column
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relationship $relation
|
||||
* @return string
|
||||
*/
|
||||
protected function getRelationHashedColumn($column, $relation)
|
||||
{
|
||||
if (str_contains($column, '.')) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
return $this->getQuery()->from === $relation->getQuery()->getQuery()->from
|
||||
? "{$relation->getRelationCountHash(false)}.$column"
|
||||
: $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to count the relations.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function withCount($relations)
|
||||
{
|
||||
return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the max of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withMax($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'max');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the min of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withMin($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the sum of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withSum($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'sum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the average of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withAvg($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'avg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the existence of related models.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function withExists($relation)
|
||||
{
|
||||
return $this->withAggregate($relation, '*', 'exists');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "has" condition where clause to the query.
|
||||
*
|
||||
@@ -216,7 +781,7 @@ trait QueriesRelationships
|
||||
$hasQuery->mergeConstraintsFrom($relation->getQuery());
|
||||
|
||||
return $this->canUseExistsForExistenceCheck($operator, $count)
|
||||
? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $not = ($operator === '<' && $count === 1))
|
||||
? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1)
|
||||
: $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
|
||||
}
|
||||
|
||||
@@ -228,9 +793,14 @@ trait QueriesRelationships
|
||||
*/
|
||||
public function mergeConstraintsFrom(Builder $from)
|
||||
{
|
||||
$whereBindings = Arr::get(
|
||||
$from->getQuery()->getRawBindings(), 'where', []
|
||||
);
|
||||
$whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
|
||||
|
||||
$wheres = $from->getQuery()->from !== $this->getQuery()->from
|
||||
? $this->requalifyWhereTables(
|
||||
$from->getQuery()->wheres,
|
||||
$from->getQuery()->from,
|
||||
$this->getModel()->getTable()
|
||||
) : $from->getQuery()->wheres;
|
||||
|
||||
// Here we have some other query that we want to merge the where constraints from. We will
|
||||
// copy over any where constraints on the query as well as remove any global scopes the
|
||||
@@ -238,14 +808,33 @@ trait QueriesRelationships
|
||||
return $this->withoutGlobalScopes(
|
||||
$from->removedScopes()
|
||||
)->mergeWheres(
|
||||
$from->getQuery()->wheres, $whereBindings
|
||||
$wheres, $whereBindings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the table name for any columns with a new qualified name.
|
||||
*
|
||||
* @param array $wheres
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @return array
|
||||
*/
|
||||
protected function requalifyWhereTables(array $wheres, string $from, string $to): array
|
||||
{
|
||||
return collect($wheres)->map(function ($where) use ($from, $to) {
|
||||
return collect($where)->map(function ($value) use ($from, $to) {
|
||||
return is_string($value) && str_starts_with($value, $from.'.')
|
||||
? $to.'.'.Str::afterLast($value, '.')
|
||||
: $value;
|
||||
});
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sub-query count clause to this query.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BelongsToManyRelationship
|
||||
{
|
||||
/**
|
||||
* The related factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The pivot attributes / attribute resolver.
|
||||
*
|
||||
* @var callable|array
|
||||
*/
|
||||
protected $pivot;
|
||||
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* Create a new attached relationship definition.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $factory
|
||||
* @param callable|array $pivot
|
||||
* @param string $relationship
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($factory, $pivot, $relationship)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->pivot = $pivot;
|
||||
$this->relationship = $relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the attached relationship for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
public function createFor(Model $model)
|
||||
{
|
||||
Collection::wrap($this->factory instanceof Factory ? $this->factory->create([], $model) : $this->factory)->each(function ($attachable) use ($model) {
|
||||
$model->{$this->relationship}()->attach(
|
||||
$attachable,
|
||||
is_callable($this->pivot) ? call_user_func($this->pivot, $model) : $this->pivot
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the model instances to always use when creating relationships.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $recycle
|
||||
* @return $this
|
||||
*/
|
||||
public function recycle($recycle)
|
||||
{
|
||||
if ($this->factory instanceof Factory) {
|
||||
$this->factory = $this->factory->recycle($recycle);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
97
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/BelongsToRelationship.php
vendored
Normal file
97
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/BelongsToRelationship.php
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class BelongsToRelationship
|
||||
{
|
||||
/**
|
||||
* The related factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* The cached, resolved parent instance ID.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $resolved;
|
||||
|
||||
/**
|
||||
* Create a new "belongs to" relationship definition.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory
|
||||
* @param string $relationship
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($factory, $relationship)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->relationship = $relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent model attributes and resolvers for the given child model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return array
|
||||
*/
|
||||
public function attributesFor(Model $model)
|
||||
{
|
||||
$relationship = $model->{$this->relationship}();
|
||||
|
||||
return $relationship instanceof MorphTo ? [
|
||||
$relationship->getMorphType() => $this->factory instanceof Factory ? $this->factory->newModel()->getMorphClass() : $this->factory->getMorphClass(),
|
||||
$relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()),
|
||||
] : [
|
||||
$relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deferred resolver for this relationship's parent ID.
|
||||
*
|
||||
* @param string|null $key
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function resolver($key)
|
||||
{
|
||||
return function () use ($key) {
|
||||
if (! $this->resolved) {
|
||||
$instance = $this->factory instanceof Factory
|
||||
? ($this->factory->getRandomRecycledModel($this->factory->modelName()) ?? $this->factory->create())
|
||||
: $this->factory;
|
||||
|
||||
return $this->resolved = $key ? $instance->{$key} : $instance->getKey();
|
||||
}
|
||||
|
||||
return $this->resolved;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the model instances to always use when creating relationships.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $recycle
|
||||
* @return $this
|
||||
*/
|
||||
public function recycle($recycle)
|
||||
{
|
||||
if ($this->factory instanceof Factory) {
|
||||
$this->factory = $this->factory->recycle($recycle);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
26
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/CrossJoinSequence.php
vendored
Normal file
26
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/CrossJoinSequence.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CrossJoinSequence extends Sequence
|
||||
{
|
||||
/**
|
||||
* Create a new cross join sequence instance.
|
||||
*
|
||||
* @param array $sequences
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(...$sequences)
|
||||
{
|
||||
$crossJoined = array_map(
|
||||
function ($a) {
|
||||
return array_merge(...$a);
|
||||
},
|
||||
Arr::crossJoin(...$sequences),
|
||||
);
|
||||
|
||||
parent::__construct(...$crossJoined);
|
||||
}
|
||||
}
|
||||
926
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php
vendored
Normal file
926
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php
vendored
Normal file
@@ -0,0 +1,926 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Closure;
|
||||
use Faker\Generator;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\Conditionable;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @method $this trashed()
|
||||
*/
|
||||
abstract class Factory
|
||||
{
|
||||
use Conditionable, ForwardsCalls, Macroable {
|
||||
__call as macroCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var class-string<\Illuminate\Database\Eloquent\Model|TModel>
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* The number of models that should be generated.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* The state transformations that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $states;
|
||||
|
||||
/**
|
||||
* The parent relationships that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $has;
|
||||
|
||||
/**
|
||||
* The child relationships that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $for;
|
||||
|
||||
/**
|
||||
* The model instances to always use when creating relationships.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $recycle;
|
||||
|
||||
/**
|
||||
* The "after making" callbacks that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $afterMaking;
|
||||
|
||||
/**
|
||||
* The "after creating" callbacks that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $afterCreating;
|
||||
|
||||
/**
|
||||
* The name of the database connection that will be used to create the models.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The current Faker instance.
|
||||
*
|
||||
* @var \Faker\Generator
|
||||
*/
|
||||
protected $faker;
|
||||
|
||||
/**
|
||||
* The default namespace where factories reside.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $namespace = 'Database\\Factories\\';
|
||||
|
||||
/**
|
||||
* The default model name resolver.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected static $modelNameResolver;
|
||||
|
||||
/**
|
||||
* The factory name resolver.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected static $factoryNameResolver;
|
||||
|
||||
/**
|
||||
* Create a new factory instance.
|
||||
*
|
||||
* @param int|null $count
|
||||
* @param \Illuminate\Support\Collection|null $states
|
||||
* @param \Illuminate\Support\Collection|null $has
|
||||
* @param \Illuminate\Support\Collection|null $for
|
||||
* @param \Illuminate\Support\Collection|null $afterMaking
|
||||
* @param \Illuminate\Support\Collection|null $afterCreating
|
||||
* @param string|null $connection
|
||||
* @param \Illuminate\Support\Collection|null $recycle
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($count = null,
|
||||
?Collection $states = null,
|
||||
?Collection $has = null,
|
||||
?Collection $for = null,
|
||||
?Collection $afterMaking = null,
|
||||
?Collection $afterCreating = null,
|
||||
$connection = null,
|
||||
?Collection $recycle = null)
|
||||
{
|
||||
$this->count = $count;
|
||||
$this->states = $states ?? new Collection;
|
||||
$this->has = $has ?? new Collection;
|
||||
$this->for = $for ?? new Collection;
|
||||
$this->afterMaking = $afterMaking ?? new Collection;
|
||||
$this->afterCreating = $afterCreating ?? new Collection;
|
||||
$this->connection = $connection;
|
||||
$this->recycle = $recycle ?? new Collection;
|
||||
$this->faker = $this->withFaker();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
abstract public function definition();
|
||||
|
||||
/**
|
||||
* Get a new factory instance for the given attributes.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @return static
|
||||
*/
|
||||
public static function new($attributes = [])
|
||||
{
|
||||
return (new static)->state($attributes)->configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new factory instance for the given number of models.
|
||||
*
|
||||
* @param int $count
|
||||
* @return static
|
||||
*/
|
||||
public static function times(int $count)
|
||||
{
|
||||
return static::new()->count($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the factory.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function configure()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw attributes generated by the factory.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function raw($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
if ($this->count === null) {
|
||||
return $this->state($attributes)->getExpandedAttributes($parent);
|
||||
}
|
||||
|
||||
return array_map(function () use ($attributes, $parent) {
|
||||
return $this->state($attributes)->getExpandedAttributes($parent);
|
||||
}, range(1, $this->count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single model and persist it to the database.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function createOne($attributes = [])
|
||||
{
|
||||
return $this->count(null)->create($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single model and persist it to the database without dispatching any model events.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function createOneQuietly($attributes = [])
|
||||
{
|
||||
return $this->count(null)->createQuietly($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database.
|
||||
*
|
||||
* @param iterable<int, array<string, mixed>> $records
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model|TModel>
|
||||
*/
|
||||
public function createMany(iterable $records)
|
||||
{
|
||||
return new EloquentCollection(
|
||||
collect($records)->map(function ($record) {
|
||||
return $this->state($record)->create();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database without dispatching any model events.
|
||||
*
|
||||
* @param iterable<int, array<string, mixed>> $records
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model|TModel>
|
||||
*/
|
||||
public function createManyQuietly(iterable $records)
|
||||
{
|
||||
return Model::withoutEvents(function () use ($records) {
|
||||
return $this->createMany($records);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model|TModel>|\Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function create($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
if (! empty($attributes)) {
|
||||
return $this->state($attributes)->create([], $parent);
|
||||
}
|
||||
|
||||
$results = $this->make($attributes, $parent);
|
||||
|
||||
if ($results instanceof Model) {
|
||||
$this->store(collect([$results]));
|
||||
|
||||
$this->callAfterCreating(collect([$results]), $parent);
|
||||
} else {
|
||||
$this->store($results);
|
||||
|
||||
$this->callAfterCreating($results, $parent);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database without dispatching any model events.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model|TModel>|\Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function createQuietly($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
return Model::withoutEvents(function () use ($attributes, $parent) {
|
||||
return $this->create($attributes, $parent);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback that persists a model in the database when invoked.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Closure(): (\Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model|TModel>|\Illuminate\Database\Eloquent\Model|TModel)
|
||||
*/
|
||||
public function lazy(array $attributes = [], ?Model $parent = null)
|
||||
{
|
||||
return fn () => $this->create($attributes, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection name on the results and store them.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @return void
|
||||
*/
|
||||
protected function store(Collection $results)
|
||||
{
|
||||
$results->each(function ($model) {
|
||||
if (! isset($this->connection)) {
|
||||
$model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName());
|
||||
}
|
||||
|
||||
$model->save();
|
||||
|
||||
foreach ($model->getRelations() as $name => $items) {
|
||||
if ($items instanceof Enumerable && $items->isEmpty()) {
|
||||
$model->unsetRelation($name);
|
||||
}
|
||||
}
|
||||
|
||||
$this->createChildren($model);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the children for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
protected function createChildren(Model $model)
|
||||
{
|
||||
Model::unguarded(function () use ($model) {
|
||||
$this->has->each(function ($has) use ($model) {
|
||||
$has->recycle($this->recycle)->createFor($model);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a single instance of the model.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function makeOne($attributes = [])
|
||||
{
|
||||
return $this->count(null)->make($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models.
|
||||
*
|
||||
* @param (callable(array<string, mixed>): array<string, mixed>)|array<string, mixed> $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model|TModel>|\Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function make($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
if (! empty($attributes)) {
|
||||
return $this->state($attributes)->make([], $parent);
|
||||
}
|
||||
|
||||
if ($this->count === null) {
|
||||
return tap($this->makeInstance($parent), function ($instance) {
|
||||
$this->callAfterMaking(collect([$instance]));
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->count < 1) {
|
||||
return $this->newModel()->newCollection();
|
||||
}
|
||||
|
||||
$instances = $this->newModel()->newCollection(array_map(function () use ($parent) {
|
||||
return $this->makeInstance($parent);
|
||||
}, range(1, $this->count)));
|
||||
|
||||
$this->callAfterMaking($instances);
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an instance of the model with the given attributes.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected function makeInstance(?Model $parent)
|
||||
{
|
||||
return Model::unguarded(function () use ($parent) {
|
||||
return tap($this->newModel($this->getExpandedAttributes($parent)), function ($instance) {
|
||||
if (isset($this->connection)) {
|
||||
$instance->setConnection($this->connection);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a raw attributes array for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getExpandedAttributes(?Model $parent)
|
||||
{
|
||||
return $this->expandAttributes($this->getRawAttributes($parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw attributes for the model as an array.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return array
|
||||
*/
|
||||
protected function getRawAttributes(?Model $parent)
|
||||
{
|
||||
return $this->states->pipe(function ($states) {
|
||||
return $this->for->isEmpty() ? $states : new Collection(array_merge([function () {
|
||||
return $this->parentResolvers();
|
||||
}], $states->all()));
|
||||
})->reduce(function ($carry, $state) use ($parent) {
|
||||
if ($state instanceof Closure) {
|
||||
$state = $state->bindTo($this);
|
||||
}
|
||||
|
||||
return array_merge($carry, $state($carry, $parent));
|
||||
}, $this->definition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the parent relationship resolvers (as deferred Closures).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parentResolvers()
|
||||
{
|
||||
$model = $this->newModel();
|
||||
|
||||
return $this->for->map(function (BelongsToRelationship $for) use ($model) {
|
||||
return $for->recycle($this->recycle)->attributesFor($model);
|
||||
})->collapse()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all attributes to their underlying values.
|
||||
*
|
||||
* @param array $definition
|
||||
* @return array
|
||||
*/
|
||||
protected function expandAttributes(array $definition)
|
||||
{
|
||||
return collect($definition)
|
||||
->map($evaluateRelations = function ($attribute) {
|
||||
if ($attribute instanceof self) {
|
||||
$attribute = $this->getRandomRecycledModel($attribute->modelName())
|
||||
?? $attribute->recycle($this->recycle)->create()->getKey();
|
||||
} elseif ($attribute instanceof Model) {
|
||||
$attribute = $attribute->getKey();
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
})
|
||||
->map(function ($attribute, $key) use (&$definition, $evaluateRelations) {
|
||||
if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
|
||||
$attribute = $attribute($definition);
|
||||
}
|
||||
|
||||
$attribute = $evaluateRelations($attribute);
|
||||
|
||||
$definition[$key] = $attribute;
|
||||
|
||||
return $attribute;
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new state transformation to the model definition.
|
||||
*
|
||||
* @param (callable(array<string, mixed>, \Illuminate\Database\Eloquent\Model|null): array<string, mixed>)|array<string, mixed> $state
|
||||
* @return static
|
||||
*/
|
||||
public function state($state)
|
||||
{
|
||||
return $this->newInstance([
|
||||
'states' => $this->states->concat([
|
||||
is_callable($state) ? $state : function () use ($state) {
|
||||
return $state;
|
||||
},
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single model attribute.
|
||||
*
|
||||
* @param string|int $key
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
return $this->state([$key => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sequenced state transformation to the model definition.
|
||||
*
|
||||
* @param array $sequence
|
||||
* @return static
|
||||
*/
|
||||
public function sequence(...$sequence)
|
||||
{
|
||||
return $this->state(new Sequence(...$sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sequenced state transformation to the model definition and update the pending creation count to the size of the sequence.
|
||||
*
|
||||
* @param array $sequence
|
||||
* @return static
|
||||
*/
|
||||
public function forEachSequence(...$sequence)
|
||||
{
|
||||
return $this->state(new Sequence(...$sequence))->count(count($sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new cross joined sequenced state transformation to the model definition.
|
||||
*
|
||||
* @param array $sequence
|
||||
* @return static
|
||||
*/
|
||||
public function crossJoinSequence(...$sequence)
|
||||
{
|
||||
return $this->state(new CrossJoinSequence(...$sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a child relationship for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory $factory
|
||||
* @param string|null $relationship
|
||||
* @return static
|
||||
*/
|
||||
public function has(self $factory, $relationship = null)
|
||||
{
|
||||
return $this->newInstance([
|
||||
'has' => $this->has->concat([new Relationship(
|
||||
$factory, $relationship ?? $this->guessRelationship($factory->modelName())
|
||||
)]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to guess the relationship name for a "has" relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @return string
|
||||
*/
|
||||
protected function guessRelationship(string $related)
|
||||
{
|
||||
$guess = Str::camel(Str::plural(class_basename($related)));
|
||||
|
||||
return method_exists($this->modelName(), $guess) ? $guess : Str::singular($guess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an attached relationship for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $factory
|
||||
* @param (callable(): array<string, mixed>)|array<string, mixed> $pivot
|
||||
* @param string|null $relationship
|
||||
* @return static
|
||||
*/
|
||||
public function hasAttached($factory, $pivot = [], $relationship = null)
|
||||
{
|
||||
return $this->newInstance([
|
||||
'has' => $this->has->concat([new BelongsToManyRelationship(
|
||||
$factory,
|
||||
$pivot,
|
||||
$relationship ?? Str::camel(Str::plural(class_basename(
|
||||
$factory instanceof Factory
|
||||
? $factory->modelName()
|
||||
: Collection::wrap($factory)->first()
|
||||
)))
|
||||
)]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a parent relationship for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory
|
||||
* @param string|null $relationship
|
||||
* @return static
|
||||
*/
|
||||
public function for($factory, $relationship = null)
|
||||
{
|
||||
return $this->newInstance(['for' => $this->for->concat([new BelongsToRelationship(
|
||||
$factory,
|
||||
$relationship ?? Str::camel(class_basename(
|
||||
$factory instanceof Factory ? $factory->modelName() : $factory
|
||||
))
|
||||
)])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide model instances to use instead of any nested factory calls when creating relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Support\Collection|array $model
|
||||
* @return static
|
||||
*/
|
||||
public function recycle($model)
|
||||
{
|
||||
// Group provided models by the type and merge them into existing recycle collection
|
||||
return $this->newInstance([
|
||||
'recycle' => $this->recycle
|
||||
->flatten()
|
||||
->merge(
|
||||
Collection::wrap($model instanceof Model ? func_get_args() : $model)
|
||||
->flatten()
|
||||
)->groupBy(fn ($model) => get_class($model)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a random model of a given type from previously provided models to recycle.
|
||||
*
|
||||
* @param string $modelClassName
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
public function getRandomRecycledModel($modelClassName)
|
||||
{
|
||||
return $this->recycle->get($modelClassName)?->random();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new "after making" callback to the model definition.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Database\Eloquent\Model|TModel): mixed $callback
|
||||
* @return static
|
||||
*/
|
||||
public function afterMaking(Closure $callback)
|
||||
{
|
||||
return $this->newInstance(['afterMaking' => $this->afterMaking->concat([$callback])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new "after creating" callback to the model definition.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Database\Eloquent\Model|TModel): mixed $callback
|
||||
* @return static
|
||||
*/
|
||||
public function afterCreating(Closure $callback)
|
||||
{
|
||||
return $this->newInstance(['afterCreating' => $this->afterCreating->concat([$callback])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the "after making" callbacks for the given model instances.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $instances
|
||||
* @return void
|
||||
*/
|
||||
protected function callAfterMaking(Collection $instances)
|
||||
{
|
||||
$instances->each(function ($model) {
|
||||
$this->afterMaking->each(function ($callback) use ($model) {
|
||||
$callback($model);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the "after creating" callbacks for the given model instances.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $instances
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return void
|
||||
*/
|
||||
protected function callAfterCreating(Collection $instances, ?Model $parent = null)
|
||||
{
|
||||
$instances->each(function ($model) use ($parent) {
|
||||
$this->afterCreating->each(function ($callback) use ($model, $parent) {
|
||||
$callback($model, $parent);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how many models should be generated.
|
||||
*
|
||||
* @param int|null $count
|
||||
* @return static
|
||||
*/
|
||||
public function count(?int $count)
|
||||
{
|
||||
return $this->newInstance(['count' => $count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the database connection that should be used to generate models.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return static
|
||||
*/
|
||||
public function connection(string $connection)
|
||||
{
|
||||
return $this->newInstance(['connection' => $connection]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the factory builder with the given mutated properties.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return static
|
||||
*/
|
||||
protected function newInstance(array $arguments = [])
|
||||
{
|
||||
return new static(...array_values(array_merge([
|
||||
'count' => $this->count,
|
||||
'states' => $this->states,
|
||||
'has' => $this->has,
|
||||
'for' => $this->for,
|
||||
'afterMaking' => $this->afterMaking,
|
||||
'afterCreating' => $this->afterCreating,
|
||||
'connection' => $this->connection,
|
||||
'recycle' => $this->recycle,
|
||||
], $arguments)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new model instance.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|TModel
|
||||
*/
|
||||
public function newModel(array $attributes = [])
|
||||
{
|
||||
$model = $this->modelName();
|
||||
|
||||
return new $model($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the model that is generated by the factory.
|
||||
*
|
||||
* @return class-string<\Illuminate\Database\Eloquent\Model|TModel>
|
||||
*/
|
||||
public function modelName()
|
||||
{
|
||||
$resolver = static::$modelNameResolver ?? function (self $factory) {
|
||||
$namespacedFactoryBasename = Str::replaceLast(
|
||||
'Factory', '', Str::replaceFirst(static::$namespace, '', get_class($factory))
|
||||
);
|
||||
|
||||
$factoryBasename = Str::replaceLast('Factory', '', class_basename($factory));
|
||||
|
||||
$appNamespace = static::appNamespace();
|
||||
|
||||
return class_exists($appNamespace.'Models\\'.$namespacedFactoryBasename)
|
||||
? $appNamespace.'Models\\'.$namespacedFactoryBasename
|
||||
: $appNamespace.$factoryBasename;
|
||||
};
|
||||
|
||||
return $this->model ?? $resolver($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the callback that should be invoked to guess model names based on factory names.
|
||||
*
|
||||
* @param callable(self): class-string<\Illuminate\Database\Eloquent\Model|TModel> $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function guessModelNamesUsing(callable $callback)
|
||||
{
|
||||
static::$modelNameResolver = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the default namespace that contains the application's model factories.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return void
|
||||
*/
|
||||
public static function useNamespace(string $namespace)
|
||||
{
|
||||
static::$namespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new factory instance for the given model name.
|
||||
*
|
||||
* @param class-string<\Illuminate\Database\Eloquent\Model> $modelName
|
||||
* @return \Illuminate\Database\Eloquent\Factories\Factory
|
||||
*/
|
||||
public static function factoryForModel(string $modelName)
|
||||
{
|
||||
$factory = static::resolveFactoryName($modelName);
|
||||
|
||||
return $factory::new();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the callback that should be invoked to guess factory names based on dynamic relationship names.
|
||||
*
|
||||
* @param callable(class-string<\Illuminate\Database\Eloquent\Model>): class-string<\Illuminate\Database\Eloquent\Factories\Factory> $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function guessFactoryNamesUsing(callable $callback)
|
||||
{
|
||||
static::$factoryNameResolver = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new Faker instance.
|
||||
*
|
||||
* @return \Faker\Generator
|
||||
*/
|
||||
protected function withFaker()
|
||||
{
|
||||
return Container::getInstance()->make(Generator::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory name for the given model name.
|
||||
*
|
||||
* @param class-string<\Illuminate\Database\Eloquent\Model> $modelName
|
||||
* @return class-string<\Illuminate\Database\Eloquent\Factories\Factory>
|
||||
*/
|
||||
public static function resolveFactoryName(string $modelName)
|
||||
{
|
||||
$resolver = static::$factoryNameResolver ?? function (string $modelName) {
|
||||
$appNamespace = static::appNamespace();
|
||||
|
||||
$modelName = Str::startsWith($modelName, $appNamespace.'Models\\')
|
||||
? Str::after($modelName, $appNamespace.'Models\\')
|
||||
: Str::after($modelName, $appNamespace);
|
||||
|
||||
return static::$namespace.$modelName.'Factory';
|
||||
};
|
||||
|
||||
return $resolver($modelName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application namespace for the application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function appNamespace()
|
||||
{
|
||||
try {
|
||||
return Container::getInstance()
|
||||
->make(Application::class)
|
||||
->getNamespace();
|
||||
} catch (Throwable $e) {
|
||||
return 'App\\';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy dynamic factory methods onto their proper methods.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (static::hasMacro($method)) {
|
||||
return $this->macroCall($method, $parameters);
|
||||
}
|
||||
|
||||
if ($method === 'trashed' && in_array(SoftDeletes::class, class_uses_recursive($this->modelName()))) {
|
||||
return $this->state([
|
||||
$this->newModel()->getDeletedAtColumn() => $parameters[0] ?? Carbon::now()->subDay(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! Str::startsWith($method, ['for', 'has'])) {
|
||||
static::throwBadMethodCallException($method);
|
||||
}
|
||||
|
||||
$relationship = Str::camel(Str::substr($method, 3));
|
||||
|
||||
$relatedModel = get_class($this->newModel()->{$relationship}()->getRelated());
|
||||
|
||||
if (method_exists($relatedModel, 'newFactory')) {
|
||||
$factory = $relatedModel::newFactory() ?? static::factoryForModel($relatedModel);
|
||||
} else {
|
||||
$factory = static::factoryForModel($relatedModel);
|
||||
}
|
||||
|
||||
if (str_starts_with($method, 'for')) {
|
||||
return $this->for($factory->state($parameters[0] ?? []), $relationship);
|
||||
} elseif (str_starts_with($method, 'has')) {
|
||||
return $this->has(
|
||||
$factory
|
||||
->count(is_numeric($parameters[0] ?? null) ? $parameters[0] : 1)
|
||||
->state((is_callable($parameters[0] ?? null) || is_array($parameters[0] ?? null)) ? $parameters[0] : ($parameters[1] ?? [])),
|
||||
$relationship
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php
vendored
Normal file
32
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
trait HasFactory
|
||||
{
|
||||
/**
|
||||
* Get a new factory instance for the model.
|
||||
*
|
||||
* @param callable|array|int|null $count
|
||||
* @param callable|array $state
|
||||
* @return \Illuminate\Database\Eloquent\Factories\Factory<static>
|
||||
*/
|
||||
public static function factory($count = null, $state = [])
|
||||
{
|
||||
$factory = static::newFactory() ?: Factory::factoryForModel(get_called_class());
|
||||
|
||||
return $factory
|
||||
->count(is_numeric($count) ? $count : null)
|
||||
->state(is_callable($count) || is_array($count) ? $count : $state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new factory instance for the model.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Factories\Factory<static>
|
||||
*/
|
||||
protected static function newFactory()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
75
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Relationship.php
vendored
Normal file
75
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Relationship.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
|
||||
|
||||
class Relationship
|
||||
{
|
||||
/**
|
||||
* The related factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Factories\Factory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* Create a new child relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory $factory
|
||||
* @param string $relationship
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Factory $factory, $relationship)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->relationship = $relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the child relationship for the given parent model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return void
|
||||
*/
|
||||
public function createFor(Model $parent)
|
||||
{
|
||||
$relationship = $parent->{$this->relationship}();
|
||||
|
||||
if ($relationship instanceof MorphOneOrMany) {
|
||||
$this->factory->state([
|
||||
$relationship->getMorphType() => $relationship->getMorphClass(),
|
||||
$relationship->getForeignKeyName() => $relationship->getParentKey(),
|
||||
])->create([], $parent);
|
||||
} elseif ($relationship instanceof HasOneOrMany) {
|
||||
$this->factory->state([
|
||||
$relationship->getForeignKeyName() => $relationship->getParentKey(),
|
||||
])->create([], $parent);
|
||||
} elseif ($relationship instanceof BelongsToMany) {
|
||||
$relationship->attach($this->factory->create([], $parent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the model instances to always use when creating relationships.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $recycle
|
||||
* @return $this
|
||||
*/
|
||||
public function recycle($recycle)
|
||||
{
|
||||
$this->factory = $this->factory->recycle($recycle);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
63
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Sequence.php
vendored
Normal file
63
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Sequence.php
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Countable;
|
||||
|
||||
class Sequence implements Countable
|
||||
{
|
||||
/**
|
||||
* The sequence of return values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sequence;
|
||||
|
||||
/**
|
||||
* The count of the sequence items.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $count;
|
||||
|
||||
/**
|
||||
* The current index of the sequence iteration.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $index = 0;
|
||||
|
||||
/**
|
||||
* Create a new sequence instance.
|
||||
*
|
||||
* @param array $sequence
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(...$sequence)
|
||||
{
|
||||
$this->sequence = $sequence;
|
||||
$this->count = count($sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current count of the sequence items.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next value in the sequence.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
return tap(value($this->sequence[$this->index % $this->count], $this), function () {
|
||||
$this->index = $this->index + 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
<?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 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 short-name.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @param callable $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function defineAs($class, $name, callable $attributes)
|
||||
{
|
||||
return $this->define($class, $attributes, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a class with a given set of attributes.
|
||||
*
|
||||
* @param string $class
|
||||
* @param callable $attributes
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function define($class, callable $attributes, $name = 'default')
|
||||
{
|
||||
$this->definitions[$class][$name] = $attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a state with a given set of attributes.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $state
|
||||
* @param callable $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function state($class, $state, callable $attributes)
|
||||
{
|
||||
$this->states[$class][$state] = $attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 and type and persist it to the database.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
*/
|
||||
public function createAs($class, $name, array $attributes = [])
|
||||
{
|
||||
return $this->of($class, $name)->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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the given model and type.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
*/
|
||||
public function makeAs($class, $name, array $attributes = [])
|
||||
{
|
||||
return $this->of($class, $name)->make($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw attribute array for a given named model.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
public function rawOf($class, $name, array $attributes = [])
|
||||
{
|
||||
return $this->raw($class, $attributes, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw attribute array for a given model.
|
||||
*
|
||||
* @param string $class
|
||||
* @param array $attributes
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function raw($class, array $attributes = [], $name = 'default')
|
||||
{
|
||||
return array_merge(
|
||||
call_user_func($this->definitions[$class][$name], $this->faker), $attributes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a builder for the given model.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @return \Illuminate\Database\Eloquent\FactoryBuilder
|
||||
*/
|
||||
public function of($class, $name = 'default')
|
||||
{
|
||||
return new FactoryBuilder($class, $name, $this->definitions, $this->states, $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)
|
||||
{
|
||||
return $this->define($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the value at the given offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->definitions[$offset]);
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Closure;
|
||||
use Faker\Generator as Faker;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
|
||||
class FactoryBuilder
|
||||
{
|
||||
use Macroable;
|
||||
|
||||
/**
|
||||
* The model definitions in the container.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions;
|
||||
|
||||
/**
|
||||
* The model being built.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $class;
|
||||
|
||||
/**
|
||||
* The name of the model being built.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'default';
|
||||
|
||||
/**
|
||||
* The model states.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $states;
|
||||
|
||||
/**
|
||||
* 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 string $name
|
||||
* @param array $definitions
|
||||
* @param array $states
|
||||
* @param \Faker\Generator $faker
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($class, $name, array $definitions, array $states, Faker $faker)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->class = $class;
|
||||
$this->faker = $faker;
|
||||
$this->states = $states;
|
||||
$this->definitions = $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mixed
|
||||
*/
|
||||
public function create(array $attributes = [])
|
||||
{
|
||||
$results = $this->make($attributes);
|
||||
|
||||
if ($results instanceof Model) {
|
||||
$this->store(collect([$results]));
|
||||
} else {
|
||||
$this->store($results);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName());
|
||||
|
||||
$model->save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
*/
|
||||
public function make(array $attributes = [])
|
||||
{
|
||||
if ($this->amount === null) {
|
||||
return $this->makeInstance($attributes);
|
||||
}
|
||||
|
||||
if ($this->amount < 1) {
|
||||
return (new $this->class)->newCollection();
|
||||
}
|
||||
|
||||
return (new $this->class)->newCollection(array_map(function () use ($attributes) {
|
||||
return $this->makeInstance($attributes);
|
||||
}, range(1, $this->amount)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected function getRawAttributes(array $attributes = [])
|
||||
{
|
||||
$definition = call_user_func(
|
||||
$this->definitions[$this->class][$this->name],
|
||||
$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
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function makeInstance(array $attributes = [])
|
||||
{
|
||||
return Model::unguarded(function () use ($attributes) {
|
||||
if (! isset($this->definitions[$this->class][$this->name])) {
|
||||
throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}].");
|
||||
}
|
||||
|
||||
return new $this->class(
|
||||
$this->getRawAttributes($attributes)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the active states to the model definition array.
|
||||
*
|
||||
* @param array $definition
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected function applyStates(array $definition, array $attributes = [])
|
||||
{
|
||||
foreach ($this->activeStates as $state) {
|
||||
if (! isset($this->states[$this->class][$state])) {
|
||||
throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}].");
|
||||
}
|
||||
|
||||
$definition = array_merge($definition, call_user_func(
|
||||
$this->states[$this->class][$state],
|
||||
$this->faker, $attributes
|
||||
));
|
||||
}
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all attributes to their underlying values.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected function expandAttributes(array $attributes)
|
||||
{
|
||||
foreach ($attributes as &$attribute) {
|
||||
if ($attribute instanceof Closure) {
|
||||
$attribute = $attribute($attributes);
|
||||
}
|
||||
|
||||
if ($attribute instanceof static) {
|
||||
$attribute = $attribute->create()->getKey();
|
||||
}
|
||||
|
||||
if ($attribute instanceof Model) {
|
||||
$attribute = $attribute->getKey();
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
50
vendor/laravel/framework/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php
vendored
Normal file
50
vendor/laravel/framework/src/Illuminate/Database/Eloquent/HigherOrderBuilderProxy.php
vendored
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
48
vendor/laravel/framework/src/Illuminate/Database/Eloquent/InvalidCastException.php
vendored
Normal file
48
vendor/laravel/framework/src/Illuminate/Database/Eloquent/InvalidCastException.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class InvalidCastException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* The name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* The name of the column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $column;
|
||||
|
||||
/**
|
||||
* The name of the cast type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $castType;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param object $model
|
||||
* @param string $column
|
||||
* @param string $castType
|
||||
* @return static
|
||||
*/
|
||||
public function __construct($model, $column, $castType)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
parent::__construct("Call to undefined cast [{$castType}] on column [{$column}] in model [{$class}].");
|
||||
|
||||
$this->model = $class;
|
||||
$this->column = $column;
|
||||
$this->castType = $castType;
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,26 @@ class JsonEncodingException extends RuntimeException
|
||||
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
|
||||
* @param string $message
|
||||
* @return static
|
||||
*/
|
||||
public static function forAttribute($model, $key, $message)
|
||||
|
||||
48
vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassPrunable.php
vendored
Normal file
48
vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassPrunable.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use LogicException;
|
||||
|
||||
trait MassPrunable
|
||||
{
|
||||
/**
|
||||
* Prune all prunable models in the database.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return int
|
||||
*/
|
||||
public function pruneAll(int $chunkSize = 1000)
|
||||
{
|
||||
$query = tap($this->prunable(), function ($query) use ($chunkSize) {
|
||||
$query->when(! $query->getQuery()->limit, function ($query) use ($chunkSize) {
|
||||
$query->limit($chunkSize);
|
||||
});
|
||||
});
|
||||
|
||||
$total = 0;
|
||||
|
||||
do {
|
||||
$total += $count = in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))
|
||||
? $query->forceDelete()
|
||||
: $query->delete();
|
||||
|
||||
if ($count > 0) {
|
||||
event(new ModelsPruned(static::class, $total));
|
||||
}
|
||||
} while ($count > 0);
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prunable model query.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function prunable()
|
||||
{
|
||||
throw new LogicException('Please implement the prunable method on your model.');
|
||||
}
|
||||
}
|
||||
23
vendor/laravel/framework/src/Illuminate/Database/Eloquent/MissingAttributeException.php
vendored
Executable file
23
vendor/laravel/framework/src/Illuminate/Database/Eloquent/MissingAttributeException.php
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use OutOfBoundsException;
|
||||
|
||||
class MissingAttributeException extends OutOfBoundsException
|
||||
{
|
||||
/**
|
||||
* Create a new missing attribute exception instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($model, $key)
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
'The attribute [%s] either does not exist or was not retrieved for model [%s].',
|
||||
$key, get_class($model)
|
||||
));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,35 +2,39 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use RuntimeException;
|
||||
use Illuminate\Database\RecordsNotFoundException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ModelNotFoundException extends RuntimeException
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
class ModelNotFoundException extends RecordsNotFoundException
|
||||
{
|
||||
/**
|
||||
* Name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
* @var class-string<TModel>
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* The affected model IDs.
|
||||
*
|
||||
* @var int|array
|
||||
* @var array<int, int|string>
|
||||
*/
|
||||
protected $ids;
|
||||
|
||||
/**
|
||||
* Set the affected Eloquent model and instance ids.
|
||||
*
|
||||
* @param string $model
|
||||
* @param int|array $ids
|
||||
* @param class-string<TModel> $model
|
||||
* @param array<int, int|string>|int|string $ids
|
||||
* @return $this
|
||||
*/
|
||||
public function setModel($model, $ids = [])
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->ids = array_wrap($ids);
|
||||
$this->ids = Arr::wrap($ids);
|
||||
|
||||
$this->message = "No query results for model [{$model}]";
|
||||
|
||||
@@ -46,7 +50,7 @@ class ModelNotFoundException extends RuntimeException
|
||||
/**
|
||||
* Get the affected Eloquent model.
|
||||
*
|
||||
* @return string
|
||||
* @return class-string<TModel>
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
@@ -56,7 +60,7 @@ class ModelNotFoundException extends RuntimeException
|
||||
/**
|
||||
* Get the affected Eloquent model IDs.
|
||||
*
|
||||
* @return int|array
|
||||
* @return array<int, int|string>
|
||||
*/
|
||||
public function getIds()
|
||||
{
|
||||
|
||||
67
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Prunable.php
vendored
Normal file
67
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Prunable.php
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use LogicException;
|
||||
|
||||
trait Prunable
|
||||
{
|
||||
/**
|
||||
* Prune all prunable models in the database.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return int
|
||||
*/
|
||||
public function pruneAll(int $chunkSize = 1000)
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
$this->prunable()
|
||||
->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($this))), function ($query) {
|
||||
$query->withTrashed();
|
||||
})->chunkById($chunkSize, function ($models) use (&$total) {
|
||||
$models->each->prune();
|
||||
|
||||
$total += $models->count();
|
||||
|
||||
event(new ModelsPruned(static::class, $total));
|
||||
});
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prunable model query.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function prunable()
|
||||
{
|
||||
throw new LogicException('Please implement the prunable method on your model.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune the model in the database.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$this->pruning();
|
||||
|
||||
return in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))
|
||||
? $this->forceDelete()
|
||||
: $this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the model for pruning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function pruning()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,41 @@ 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 mixed $model
|
||||
* @param object $model
|
||||
* @param string $relation
|
||||
* @param string|null $type
|
||||
* @return static
|
||||
*/
|
||||
public static function make($model, $relation)
|
||||
public static function make($model, $relation, $type = null)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
return new static("Call to undefined relationship [{$relation}] on model [{$class}].");
|
||||
$instance = new static(
|
||||
is_null($type)
|
||||
? "Call to undefined relationship [{$relation}] on model [{$class}]."
|
||||
: "Call to undefined relationship [{$relation}] on model [{$class}] of type [{$type}].",
|
||||
);
|
||||
|
||||
$instance->model = $class;
|
||||
$instance->relation = $relation;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,23 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesRelatedModels;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
|
||||
|
||||
class BelongsTo extends Relation
|
||||
{
|
||||
use SupportsDefaultModels;
|
||||
use ComparesRelatedModels,
|
||||
InteractsWithDictionary,
|
||||
SupportsDefaultModels;
|
||||
|
||||
/**
|
||||
* The child model instance of the relation.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $child;
|
||||
|
||||
@@ -35,14 +41,7 @@ class BelongsTo extends Relation
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relation;
|
||||
|
||||
/**
|
||||
* The count of self joins.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $selfJoinCount = 0;
|
||||
protected $relationName;
|
||||
|
||||
/**
|
||||
* Create a new belongs to relationship instance.
|
||||
@@ -51,13 +50,13 @@ class BelongsTo extends Relation
|
||||
* @param \Illuminate\Database\Eloquent\Model $child
|
||||
* @param string $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $relation
|
||||
* @param string $relationName
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
|
||||
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName)
|
||||
{
|
||||
$this->ownerKey = $ownerKey;
|
||||
$this->relation = $relation;
|
||||
$this->relationName = $relationName;
|
||||
$this->foreignKey = $foreignKey;
|
||||
|
||||
// In the underlying base relationship class, this variable is referred to as
|
||||
@@ -75,6 +74,10 @@ class BelongsTo extends Relation
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
if (is_null($this->child->{$this->foreignKey})) {
|
||||
return $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
return $this->query->first() ?: $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
@@ -108,7 +111,9 @@ class BelongsTo extends Relation
|
||||
// our eagerly loading query so it returns the proper models from execution.
|
||||
$key = $this->related->getTable().'.'.$this->ownerKey;
|
||||
|
||||
$this->query->whereIn($key, $this->getEagerModelKeys($models));
|
||||
$whereIn = $this->whereInMethod($this->related, $this->ownerKey);
|
||||
|
||||
$this->query->{$whereIn}($key, $this->getEagerModelKeys($models));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,13 +135,6 @@ class BelongsTo extends Relation
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no keys that were not null we will just return an array with null
|
||||
// so this query wont fail plus returns zero results, which should be what the
|
||||
// developer expects to happen in this situation. Otherwise we'll sort them.
|
||||
if (count($keys) === 0) {
|
||||
return [null];
|
||||
}
|
||||
|
||||
sort($keys);
|
||||
|
||||
return array_values(array_unique($keys));
|
||||
@@ -145,7 +143,7 @@ class BelongsTo extends Relation
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
@@ -161,7 +159,7 @@ class BelongsTo extends Relation
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
@@ -178,36 +176,29 @@ class BelongsTo extends Relation
|
||||
$dictionary = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$dictionary[$result->getAttribute($owner)] = $result;
|
||||
$attribute = $this->getDictionaryKey($result->getAttribute($owner));
|
||||
|
||||
$dictionary[$attribute] = $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}]);
|
||||
$attribute = $this->getDictionaryKey($model->{$foreign});
|
||||
|
||||
if (isset($dictionary[$attribute])) {
|
||||
$model->setRelation($relation, $dictionary[$attribute]);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the parent model on the relationship.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(array $attributes)
|
||||
{
|
||||
return $this->getResults()->fill($attributes)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the model instance to the given parent.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|int|string $model
|
||||
* @param \Illuminate\Database\Eloquent\Model|int|string|null $model
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function associate($model)
|
||||
@@ -217,7 +208,9 @@ class BelongsTo extends Relation
|
||||
$this->child->setAttribute($this->foreignKey, $ownerKey);
|
||||
|
||||
if ($model instanceof Model) {
|
||||
$this->child->setRelation($this->relation, $model);
|
||||
$this->child->setRelation($this->relationName, $model);
|
||||
} else {
|
||||
$this->child->unsetRelation($this->relationName);
|
||||
}
|
||||
|
||||
return $this->child;
|
||||
@@ -232,7 +225,17 @@ class BelongsTo extends Relation
|
||||
{
|
||||
$this->child->setAttribute($this->foreignKey, null);
|
||||
|
||||
return $this->child->setRelation($this->relation, null);
|
||||
return $this->child->setRelation($this->relationName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of "dissociate" method.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function disassociate()
|
||||
{
|
||||
return $this->dissociate();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +253,7 @@ class BelongsTo extends Relation
|
||||
}
|
||||
|
||||
return $query->select($columns)->whereColumn(
|
||||
$this->getQualifiedForeignKey(), '=', $query->getModel()->getTable().'.'.$this->ownerKey
|
||||
$this->getQualifiedForeignKeyName(), '=', $query->qualifyColumn($this->ownerKey)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -271,20 +274,10 @@ class BelongsTo extends Relation
|
||||
$query->getModel()->setTable($hash);
|
||||
|
||||
return $query->whereColumn(
|
||||
$hash.'.'.$query->getModel()->getKeyName(), '=', $this->getQualifiedForeignKey()
|
||||
$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.
|
||||
*
|
||||
@@ -293,7 +286,7 @@ class BelongsTo extends Relation
|
||||
protected function relationHasIncrementingId()
|
||||
{
|
||||
return $this->related->getIncrementing() &&
|
||||
$this->related->getKeyType() === 'int';
|
||||
in_array($this->related->getKeyType(), ['int', 'integer']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,12 +300,22 @@ class BelongsTo extends Relation
|
||||
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 getForeignKey()
|
||||
public function getForeignKeyName()
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
@@ -322,9 +325,19 @@ class BelongsTo extends Relation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedForeignKey()
|
||||
public function getQualifiedForeignKeyName()
|
||||
{
|
||||
return $this->child->getTable().'.'.$this->foreignKey;
|
||||
return $this->child->qualifyColumn($this->foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key value of the child's foreign key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParentKey()
|
||||
{
|
||||
return $this->child->{$this->foreignKey};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,7 +345,7 @@ class BelongsTo extends Relation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOwnerKey()
|
||||
public function getOwnerKeyName()
|
||||
{
|
||||
return $this->ownerKey;
|
||||
}
|
||||
@@ -344,7 +357,18 @@ class BelongsTo extends Relation
|
||||
*/
|
||||
public function getQualifiedOwnerKeyName()
|
||||
{
|
||||
return $this->related->getTable().'.'.$this->ownerKey;
|
||||
return $this->related->qualifyColumn($this->ownerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's associated key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelatedKeyFrom(Model $model)
|
||||
{
|
||||
return $model->{$this->ownerKey};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,8 +376,8 @@ class BelongsTo extends Relation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelation()
|
||||
public function getRelationName()
|
||||
{
|
||||
return $this->relation;
|
||||
return $this->relationName;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
333
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php
vendored
Normal file
333
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/AsPivot.php
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
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(
|
||||
array_merge($instance->getRawOriginal(), $attributes), $exists
|
||||
);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a select query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSelectQuery($query)
|
||||
{
|
||||
if (isset($this->attributes[$this->getKeyName()])) {
|
||||
return parent::setKeysForSelectQuery($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)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a save update query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSaveQuery($query)
|
||||
{
|
||||
return $this->setKeysForSelectQuery($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;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
310
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php
vendored
Normal file
310
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/CanBeOneOfMany.php
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait CanBeOneOfMany
|
||||
{
|
||||
/**
|
||||
* Determines whether the relationship is one-of-many.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isOneOfMany = false;
|
||||
|
||||
/**
|
||||
* The name of the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationName;
|
||||
|
||||
/**
|
||||
* The one of many inner join subselect query builder instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder|null
|
||||
*/
|
||||
protected $oneOfManySubQuery;
|
||||
|
||||
/**
|
||||
* Add constraints for inner join subselect for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return void
|
||||
*/
|
||||
abstract public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null);
|
||||
|
||||
/**
|
||||
* Get the columns the determine the relationship groups.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
abstract public function getOneOfManySubQuerySelectColumns();
|
||||
|
||||
/**
|
||||
* Add join query constraints for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\JoinClause $join
|
||||
* @return void
|
||||
*/
|
||||
abstract public function addOneOfManyJoinSubQueryConstraints(JoinClause $join);
|
||||
|
||||
/**
|
||||
* Indicate that the relation is a single result of a larger one-to-many relationship.
|
||||
*
|
||||
* @param string|array|null $column
|
||||
* @param string|\Closure|null $aggregate
|
||||
* @param string|null $relation
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null)
|
||||
{
|
||||
$this->isOneOfMany = true;
|
||||
|
||||
$this->relationName = $relation ?: $this->getDefaultOneOfManyJoinAlias(
|
||||
$this->guessRelationship()
|
||||
);
|
||||
|
||||
$keyName = $this->query->getModel()->getKeyName();
|
||||
|
||||
$columns = is_string($columns = $column) ? [
|
||||
$column => $aggregate,
|
||||
$keyName => $aggregate,
|
||||
] : $column;
|
||||
|
||||
if (! array_key_exists($keyName, $columns)) {
|
||||
$columns[$keyName] = 'MAX';
|
||||
}
|
||||
|
||||
if ($aggregate instanceof Closure) {
|
||||
$closure = $aggregate;
|
||||
}
|
||||
|
||||
foreach ($columns as $column => $aggregate) {
|
||||
if (! in_array(strtolower($aggregate), ['min', 'max'])) {
|
||||
throw new InvalidArgumentException("Invalid aggregate [{$aggregate}] used within ofMany relation. Available aggregates: MIN, MAX");
|
||||
}
|
||||
|
||||
$subQuery = $this->newOneOfManySubQuery(
|
||||
$this->getOneOfManySubQuerySelectColumns(),
|
||||
$column, $aggregate
|
||||
);
|
||||
|
||||
if (isset($previous)) {
|
||||
$this->addOneOfManyJoinSubQuery($subQuery, $previous['subQuery'], $previous['column']);
|
||||
}
|
||||
|
||||
if (isset($closure)) {
|
||||
$closure($subQuery);
|
||||
}
|
||||
|
||||
if (! isset($previous)) {
|
||||
$this->oneOfManySubQuery = $subQuery;
|
||||
}
|
||||
|
||||
if (array_key_last($columns) == $column) {
|
||||
$this->addOneOfManyJoinSubQuery($this->query, $subQuery, $column);
|
||||
}
|
||||
|
||||
$previous = [
|
||||
'subQuery' => $subQuery,
|
||||
'column' => $column,
|
||||
];
|
||||
}
|
||||
|
||||
$this->addConstraints();
|
||||
|
||||
$columns = $this->query->getQuery()->columns;
|
||||
|
||||
if (is_null($columns) || $columns === ['*']) {
|
||||
$this->select([$this->qualifyColumn('*')]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the relation is the latest single result of a larger one-to-many relationship.
|
||||
*
|
||||
* @param string|array|null $column
|
||||
* @param string|null $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function latestOfMany($column = 'id', $relation = null)
|
||||
{
|
||||
return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) {
|
||||
return [$column => 'MAX'];
|
||||
})->all(), 'MAX', $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the relation is the oldest single result of a larger one-to-many relationship.
|
||||
*
|
||||
* @param string|array|null $column
|
||||
* @param string|null $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function oldestOfMany($column = 'id', $relation = null)
|
||||
{
|
||||
return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) {
|
||||
return [$column => 'MIN'];
|
||||
})->all(), 'MIN', $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default alias for the one of many inner join clause.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultOneOfManyJoinAlias($relation)
|
||||
{
|
||||
return $relation == $this->query->getModel()->getTable()
|
||||
? $relation.'_of_many'
|
||||
: $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query for the related model, grouping the query by the given column, often the foreign key of the relationship.
|
||||
*
|
||||
* @param string|array $groupBy
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function newOneOfManySubQuery($groupBy, $column = null, $aggregate = null)
|
||||
{
|
||||
$subQuery = $this->query->getModel()
|
||||
->newQuery()
|
||||
->withoutGlobalScopes($this->removedScopes());
|
||||
|
||||
foreach (Arr::wrap($groupBy) as $group) {
|
||||
$subQuery->groupBy($this->qualifyRelatedColumn($group));
|
||||
}
|
||||
|
||||
if (! is_null($column)) {
|
||||
$subQuery->selectRaw($aggregate.'('.$subQuery->getQuery()->grammar->wrap($subQuery->qualifyColumn($column)).') as '.$subQuery->getQuery()->grammar->wrap($column.'_aggregate'));
|
||||
}
|
||||
|
||||
$this->addOneOfManySubQueryConstraints($subQuery, $groupBy, $column, $aggregate);
|
||||
|
||||
return $subQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the join subquery to the given query on the given column and the relationship's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parent
|
||||
* @param \Illuminate\Database\Eloquent\Builder $subQuery
|
||||
* @param string $on
|
||||
* @return void
|
||||
*/
|
||||
protected function addOneOfManyJoinSubQuery(Builder $parent, Builder $subQuery, $on)
|
||||
{
|
||||
$parent->beforeQuery(function ($parent) use ($subQuery, $on) {
|
||||
$subQuery->applyBeforeQueryCallbacks();
|
||||
|
||||
$parent->joinSub($subQuery, $this->relationName, function ($join) use ($on) {
|
||||
$join->on($this->qualifySubSelectColumn($on.'_aggregate'), '=', $this->qualifyRelatedColumn($on));
|
||||
|
||||
$this->addOneOfManyJoinSubQueryConstraints($join, $on);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the relationship query joins to the given query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeOneOfManyJoinsTo(Builder $query)
|
||||
{
|
||||
$query->getQuery()->beforeQueryCallbacks = $this->query->getQuery()->beforeQueryCallbacks;
|
||||
|
||||
$query->applyBeforeQueryCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query builder that will contain the relationship constraints.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function getRelationQuery()
|
||||
{
|
||||
return $this->isOneOfMany()
|
||||
? $this->oneOfManySubQuery
|
||||
: $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the one of many inner join subselect builder instance.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder|void
|
||||
*/
|
||||
public function getOneOfManySubQuery()
|
||||
{
|
||||
return $this->oneOfManySubQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified column name for the one-of-many relationship using the subselect join query's alias.
|
||||
*
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
public function qualifySubSelectColumn($column)
|
||||
{
|
||||
return $this->getRelationName().'.'.last(explode('.', $column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify related column using the related table name if it is not already qualified.
|
||||
*
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
protected function qualifyRelatedColumn($column)
|
||||
{
|
||||
return str_contains($column, '.') ? $column : $this->query->getModel()->getTable().'.'.$column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the "hasOne" relationship's name via backtrace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function guessRelationship()
|
||||
{
|
||||
return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the relationship is a one-of-many relationship.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOneOfMany()
|
||||
{
|
||||
return $this->isOneOfMany;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationName()
|
||||
{
|
||||
return $this->relationName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\SupportsPartialRelations;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
trait ComparesRelatedModels
|
||||
{
|
||||
/**
|
||||
* Determine if the model is the related instance of the relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $model
|
||||
* @return bool
|
||||
*/
|
||||
public function is($model)
|
||||
{
|
||||
$match = ! is_null($model) &&
|
||||
$this->compareKeys($this->getParentKey(), $this->getRelatedKeyFrom($model)) &&
|
||||
$this->related->getTable() === $model->getTable() &&
|
||||
$this->related->getConnectionName() === $model->getConnectionName();
|
||||
|
||||
if ($match && $this instanceof SupportsPartialRelations && $this->isOneOfMany()) {
|
||||
return $this->query
|
||||
->whereKey($model->getKey())
|
||||
->exists();
|
||||
}
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is not the related instance of the relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $model
|
||||
* @return bool
|
||||
*/
|
||||
public function isNot($model)
|
||||
{
|
||||
return ! $this->is($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the parent model's key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function getParentKey();
|
||||
|
||||
/**
|
||||
* Get the value of the model's related key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getRelatedKeyFrom(Model $model);
|
||||
|
||||
/**
|
||||
* Compare the parent key with the related key.
|
||||
*
|
||||
* @param mixed $parentKey
|
||||
* @param mixed $relatedKey
|
||||
* @return bool
|
||||
*/
|
||||
protected function compareKeys($parentKey, $relatedKey)
|
||||
{
|
||||
if (empty($parentKey) || empty($relatedKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_int($parentKey) || is_int($relatedKey)) {
|
||||
return (int) $parentKey === (int) $relatedKey;
|
||||
}
|
||||
|
||||
return $parentKey === $relatedKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\Instantiator\Exception\InvalidArgumentException;
|
||||
use UnitEnum;
|
||||
|
||||
trait InteractsWithDictionary
|
||||
{
|
||||
/**
|
||||
* Get a dictionary key attribute - casting it to a string if necessary.
|
||||
*
|
||||
* @param mixed $attribute
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Doctrine\Instantiator\Exception\InvalidArgumentException
|
||||
*/
|
||||
protected function getDictionaryKey($attribute)
|
||||
{
|
||||
if (is_object($attribute)) {
|
||||
if (method_exists($attribute, '__toString')) {
|
||||
return $attribute->__toString();
|
||||
}
|
||||
|
||||
if (function_exists('enum_exists') &&
|
||||
$attribute instanceof UnitEnum) {
|
||||
return $attribute instanceof BackedEnum ? $attribute->value : $attribute->name;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Model attribute value is an object but does not have a __toString method.');
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
|
||||
trait InteractsWithPivotTable
|
||||
@@ -14,7 +15,7 @@ trait InteractsWithPivotTable
|
||||
* Each existing model is detached, and non existing ones are attached.
|
||||
*
|
||||
* @param mixed $ids
|
||||
* @param bool $touch
|
||||
* @param bool $touch
|
||||
* @return array
|
||||
*/
|
||||
public function toggle($ids, $touch = true)
|
||||
@@ -23,13 +24,13 @@ trait InteractsWithPivotTable
|
||||
'attached' => [], 'detached' => [],
|
||||
];
|
||||
|
||||
$records = $this->formatRecordsList((array) $this->parseIds($ids));
|
||||
$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->relatedKey)->all(),
|
||||
$this->newPivotQuery()->pluck($this->relatedPivotKey)->all(),
|
||||
array_keys($records)
|
||||
));
|
||||
|
||||
@@ -64,7 +65,7 @@ trait InteractsWithPivotTable
|
||||
/**
|
||||
* Sync the intermediate tables with a list of IDs without detaching.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection|array $ids
|
||||
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
|
||||
* @return array
|
||||
*/
|
||||
public function syncWithoutDetaching($ids)
|
||||
@@ -75,8 +76,8 @@ trait InteractsWithPivotTable
|
||||
/**
|
||||
* Sync the intermediate tables with a list of IDs or collection of models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|array $ids
|
||||
* @param bool $detaching
|
||||
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
|
||||
* @param bool $detaching
|
||||
* @return array
|
||||
*/
|
||||
public function sync($ids, $detaching = true)
|
||||
@@ -88,21 +89,22 @@ trait InteractsWithPivotTable
|
||||
// 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->newPivotQuery()->pluck(
|
||||
$this->relatedKey
|
||||
)->all();
|
||||
$current = $this->getCurrentlyAttachedPivots()
|
||||
->pluck($this->relatedPivotKey)->all();
|
||||
|
||||
$detach = array_diff($current, array_keys(
|
||||
$records = $this->formatRecordsList((array) $this->parseIds($ids))
|
||||
));
|
||||
$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);
|
||||
if ($detaching) {
|
||||
$detach = array_diff($current, array_keys($records));
|
||||
|
||||
$changes['detached'] = $this->castKeys($detach);
|
||||
if (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
|
||||
@@ -116,13 +118,29 @@ trait InteractsWithPivotTable
|
||||
// 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'])) {
|
||||
count($changes['updated']) ||
|
||||
count($changes['detached'])) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the intermediate tables with a list of IDs or collection of models with the given pivot values.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
|
||||
* @param array $values
|
||||
* @param bool $detaching
|
||||
* @return array
|
||||
*/
|
||||
public function syncWithPivotValues($ids, array $values, bool $detaching = true)
|
||||
{
|
||||
return $this->sync(collect($this->parseIds($ids))->mapWithKeys(function ($id) use ($values) {
|
||||
return [$id => $values];
|
||||
}), $detaching);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the sync / toggle record list so that it is keyed by ID.
|
||||
*
|
||||
@@ -133,7 +151,7 @@ trait InteractsWithPivotTable
|
||||
{
|
||||
return collect($records)->mapWithKeys(function ($attributes, $id) {
|
||||
if (! is_array($attributes)) {
|
||||
list($id, $attributes) = [$attributes, []];
|
||||
[$id, $attributes] = [$attributes, []];
|
||||
}
|
||||
|
||||
return [$id => $attributes];
|
||||
@@ -145,7 +163,7 @@ trait InteractsWithPivotTable
|
||||
*
|
||||
* @param array $records
|
||||
* @param array $current
|
||||
* @param bool $touch
|
||||
* @param bool $touch
|
||||
* @return array
|
||||
*/
|
||||
protected function attachNew(array $records, array $current, $touch = true)
|
||||
@@ -179,16 +197,25 @@ trait InteractsWithPivotTable
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $attributes
|
||||
* @param bool $touch
|
||||
* @param bool $touch
|
||||
* @return int
|
||||
*/
|
||||
public function updateExistingPivot($id, array $attributes, $touch = true)
|
||||
{
|
||||
if (in_array($this->updatedAt(), $this->pivotColumns)) {
|
||||
if ($this->using &&
|
||||
empty($this->pivotWheres) &&
|
||||
empty($this->pivotWhereIns) &&
|
||||
empty($this->pivotWhereNulls)) {
|
||||
return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch);
|
||||
}
|
||||
|
||||
if ($this->hasPivotColumn($this->updatedAt())) {
|
||||
$attributes = $this->addTimestampsToAttachment($attributes, true);
|
||||
}
|
||||
|
||||
$updated = $this->newPivotStatementForId($id)->update($attributes);
|
||||
$updated = $this->newPivotStatementForId($this->parseId($id))->update(
|
||||
$this->castAttributes($attributes)
|
||||
);
|
||||
|
||||
if ($touch) {
|
||||
$this->touchIfTouching();
|
||||
@@ -197,28 +224,78 @@ trait InteractsWithPivotTable
|
||||
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
|
||||
* @param bool $touch
|
||||
* @return void
|
||||
*/
|
||||
public function attach($id, array $attributes = [], $touch = true)
|
||||
{
|
||||
// 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(
|
||||
(array) $this->parseIds($id), $attributes
|
||||
));
|
||||
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.
|
||||
*
|
||||
@@ -248,18 +325,18 @@ trait InteractsWithPivotTable
|
||||
/**
|
||||
* Create a full attachment record payload.
|
||||
*
|
||||
* @param int $key
|
||||
* @param int $key
|
||||
* @param mixed $value
|
||||
* @param array $attributes
|
||||
* @param bool $hasTimestamps
|
||||
* @param bool $hasTimestamps
|
||||
* @return array
|
||||
*/
|
||||
protected function formatAttachRecord($key, $value, $attributes, $hasTimestamps)
|
||||
{
|
||||
list($id, $attributes) = $this->extractAttachIdAndAttributes($key, $value, $attributes);
|
||||
[$id, $attributes] = $this->extractAttachIdAndAttributes($key, $value, $attributes);
|
||||
|
||||
return array_merge(
|
||||
$this->baseAttachRecord($id, $hasTimestamps), $attributes
|
||||
$this->baseAttachRecord($id, $hasTimestamps), $this->castAttributes($attributes)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -281,15 +358,15 @@ trait InteractsWithPivotTable
|
||||
/**
|
||||
* Create a new pivot attachment record.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $id
|
||||
* @param bool $timed
|
||||
* @return array
|
||||
*/
|
||||
protected function baseAttachRecord($id, $timed)
|
||||
{
|
||||
$record[$this->relatedKey] = $id;
|
||||
$record[$this->relatedPivotKey] = $id;
|
||||
|
||||
$record[$this->foreignKey] = $this->parent->getKey();
|
||||
$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
|
||||
@@ -298,6 +375,10 @@ trait InteractsWithPivotTable
|
||||
$record = $this->addTimestampsToAttachment($record);
|
||||
}
|
||||
|
||||
foreach ($this->pivotValues as $value) {
|
||||
$record[$value['column']] = $value['value'];
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
@@ -305,13 +386,19 @@ trait InteractsWithPivotTable
|
||||
* Set the creation and update timestamps on an attach record.
|
||||
*
|
||||
* @param array $record
|
||||
* @param bool $exists
|
||||
* @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;
|
||||
}
|
||||
@@ -329,7 +416,7 @@ trait InteractsWithPivotTable
|
||||
* @param string $column
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasPivotColumn($column)
|
||||
public function hasPivotColumn($column)
|
||||
{
|
||||
return in_array($column, $this->pivotColumns);
|
||||
}
|
||||
@@ -343,24 +430,34 @@ trait InteractsWithPivotTable
|
||||
*/
|
||||
public function detach($ids = null, $touch = true)
|
||||
{
|
||||
$query = $this->newPivotQuery();
|
||||
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 = $this->parseIds($ids))) {
|
||||
if (count($ids) === 0) {
|
||||
return 0;
|
||||
// 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->getQualifiedRelatedPivotKeyName(), (array) $ids);
|
||||
}
|
||||
|
||||
$query->whereIn($this->relatedKey, (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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -368,11 +465,47 @@ trait InteractsWithPivotTable
|
||||
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 ?: 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
|
||||
* @param bool $exists
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Pivot
|
||||
*/
|
||||
public function newPivot(array $attributes = [], $exists = false)
|
||||
@@ -381,7 +514,7 @@ trait InteractsWithPivotTable
|
||||
$this->parent, $attributes, $this->table, $exists, $this->using
|
||||
);
|
||||
|
||||
return $pivot->setPivotKeys($this->foreignKey, $this->relatedKey);
|
||||
return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,7 +546,7 @@ trait InteractsWithPivotTable
|
||||
*/
|
||||
public function newPivotStatementForId($id)
|
||||
{
|
||||
return $this->newPivotQuery()->where($this->relatedKey, $id);
|
||||
return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,19 +554,23 @@ trait InteractsWithPivotTable
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
protected function newPivotQuery()
|
||||
public function newPivotQuery()
|
||||
{
|
||||
$query = $this->newPivotStatement();
|
||||
|
||||
foreach ($this->pivotWheres as $arguments) {
|
||||
call_user_func_array([$query, 'where'], $arguments);
|
||||
$query->where(...$arguments);
|
||||
}
|
||||
|
||||
foreach ($this->pivotWhereIns as $arguments) {
|
||||
call_user_func_array([$query, 'whereIn'], $arguments);
|
||||
$query->whereIn(...$arguments);
|
||||
}
|
||||
|
||||
return $query->where($this->foreignKey, $this->parent->getKey());
|
||||
foreach ($this->pivotWhereNulls as $arguments) {
|
||||
$query->whereNull(...$arguments);
|
||||
}
|
||||
|
||||
return $query->where($this->getQualifiedForeignPivotKeyName(), $this->parent->{$this->parentKey});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,18 +597,29 @@ trait InteractsWithPivotTable
|
||||
protected function parseIds($value)
|
||||
{
|
||||
if ($value instanceof Model) {
|
||||
return $value->getKey();
|
||||
return [$value->{$this->relatedKey}];
|
||||
}
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
return $value->modelKeys();
|
||||
return $value->pluck($this->relatedKey)->all();
|
||||
}
|
||||
|
||||
if ($value instanceof BaseCollection) {
|
||||
return $value->toArray();
|
||||
}
|
||||
|
||||
return $value;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,19 +630,52 @@ trait InteractsWithPivotTable
|
||||
*/
|
||||
protected function castKeys(array $keys)
|
||||
{
|
||||
return (array) array_map(function ($v) {
|
||||
return array_map(function ($v) {
|
||||
return $this->castKey($v);
|
||||
}, $keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the given key to an integer if it is numeric.
|
||||
* Cast the given key to convert to primary key type.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return mixed
|
||||
*/
|
||||
protected function castKey($key)
|
||||
{
|
||||
return is_numeric($key) ? (int) $key : (string) $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)
|
||||
{
|
||||
return match (strtolower($type)) {
|
||||
'int', 'integer' => (int) $value,
|
||||
'real', 'float', 'double' => (float) $value,
|
||||
'string' => (string) $value,
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ trait SupportsDefaultModels
|
||||
$instance = $this->newRelatedInstanceFor($parent);
|
||||
|
||||
if (is_callable($this->withDefault)) {
|
||||
return call_user_func($this->withDefault, $instance) ?: $instance;
|
||||
return call_user_func($this->withDefault, $instance, $parent) ?: $instance;
|
||||
}
|
||||
|
||||
if (is_array($this->withDefault)) {
|
||||
|
||||
@@ -13,13 +13,15 @@ class HasMany extends HasOneOrMany
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return $this->query->get();
|
||||
return ! is_null($this->getParentKey())
|
||||
? $this->query->get()
|
||||
: $this->related->newCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
@@ -35,7 +37,7 @@ class HasMany extends HasOneOrMany
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class HasManyThrough extends Relation
|
||||
{
|
||||
use InteractsWithDictionary;
|
||||
|
||||
/**
|
||||
* The "through" parent model instance.
|
||||
*
|
||||
@@ -45,6 +50,13 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
protected $localKey;
|
||||
|
||||
/**
|
||||
* The local key on the intermediary model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $secondLocalKey;
|
||||
|
||||
/**
|
||||
* Create a new has many through relationship instance.
|
||||
*
|
||||
@@ -54,15 +66,17 @@ class HasManyThrough extends Relation
|
||||
* @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)
|
||||
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);
|
||||
}
|
||||
@@ -98,10 +112,22 @@ class HasManyThrough extends Relation
|
||||
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey);
|
||||
|
||||
if ($this->throughParentSoftDeletes()) {
|
||||
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
|
||||
$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.
|
||||
*
|
||||
@@ -109,9 +135,19 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function throughParentSoftDeletes()
|
||||
{
|
||||
return in_array(SoftDeletes::class, class_uses_recursive(
|
||||
get_class($this->throughParent)
|
||||
));
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +158,9 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
$this->query->whereIn(
|
||||
$whereIn = $this->whereInMethod($this->farParent, $this->localKey);
|
||||
|
||||
$this->query->{$whereIn}(
|
||||
$this->getQualifiedFirstKeyName(), $this->getKeys($models, $this->localKey)
|
||||
);
|
||||
}
|
||||
@@ -130,7 +168,7 @@ class HasManyThrough extends Relation
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
@@ -146,7 +184,7 @@ class HasManyThrough extends Relation
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
@@ -159,7 +197,7 @@ class HasManyThrough extends Relation
|
||||
// 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->getKey()])) {
|
||||
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
|
||||
$model->setRelation(
|
||||
$relation, $this->related->newCollection($dictionary[$key])
|
||||
);
|
||||
@@ -183,7 +221,7 @@ class HasManyThrough extends Relation
|
||||
// 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->{$this->firstKey}][] = $result;
|
||||
$dictionary[$result->laravel_through_key][] = $result;
|
||||
}
|
||||
|
||||
return $dictionary;
|
||||
@@ -220,10 +258,24 @@ class HasManyThrough extends Relation
|
||||
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
|
||||
* @param array $columns
|
||||
* @return mixed
|
||||
*/
|
||||
public function first($columns = ['*'])
|
||||
@@ -239,7 +291,7 @@ class HasManyThrough extends Relation
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
|
||||
*/
|
||||
public function firstOrFail($columns = ['*'])
|
||||
{
|
||||
@@ -250,6 +302,28 @@ class HasManyThrough extends Relation
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result or call a callback.
|
||||
*
|
||||
* @param \Closure|array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Model|static|mixed
|
||||
*/
|
||||
public function firstOr($columns = ['*'], Closure $callback = null)
|
||||
{
|
||||
if ($columns instanceof Closure) {
|
||||
$callback = $columns;
|
||||
|
||||
$columns = ['*'];
|
||||
}
|
||||
|
||||
if (! is_null($model = $this->first($columns))) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
return $callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key.
|
||||
*
|
||||
@@ -259,7 +333,7 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function find($id, $columns = ['*'])
|
||||
{
|
||||
if (is_array($id)) {
|
||||
if (is_array($id) || $id instanceof Arrayable) {
|
||||
return $this->findMany($id, $columns);
|
||||
}
|
||||
|
||||
@@ -271,12 +345,14 @@ class HasManyThrough extends Relation
|
||||
/**
|
||||
* Find multiple related models by their primary keys.
|
||||
*
|
||||
* @param mixed $ids
|
||||
* @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();
|
||||
}
|
||||
@@ -293,21 +369,54 @@ class HasManyThrough extends Relation
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
|
||||
*/
|
||||
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))) {
|
||||
if (count($result) === count(array_unique($id))) {
|
||||
return $result;
|
||||
}
|
||||
} elseif (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related));
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key or call a callback.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param \Closure|array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed
|
||||
*/
|
||||
public function findOr($id, $columns = ['*'], Closure $callback = null)
|
||||
{
|
||||
if ($columns instanceof Closure) {
|
||||
$callback = $columns;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
return $callback();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,7 +426,9 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return $this->get();
|
||||
return ! is_null($this->farParent->{$this->localKey})
|
||||
? $this->get()
|
||||
: $this->related->newCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,16 +439,9 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function get($columns = ['*'])
|
||||
{
|
||||
// First we'll add the proper select columns onto the query so it is run with
|
||||
// the proper columns. Then, we will get the results and hydrate out pivot
|
||||
// models with the result of those columns as a separate model relation.
|
||||
$columns = $this->query->getQuery()->columns ? [] : $columns;
|
||||
$builder = $this->prepareQueryBuilder($columns);
|
||||
|
||||
$builder = $this->query->applyScopes();
|
||||
|
||||
$models = $builder->addSelect(
|
||||
$this->shouldSelect($columns)
|
||||
)->getModels();
|
||||
$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
|
||||
@@ -352,7 +456,7 @@ class HasManyThrough extends Relation
|
||||
/**
|
||||
* Get a paginator for the "select" statement.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int $page
|
||||
@@ -368,7 +472,7 @@ class HasManyThrough extends Relation
|
||||
/**
|
||||
* Paginate the given query into a simple paginator.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
@@ -381,6 +485,22 @@ class HasManyThrough extends Relation
|
||||
return $this->query->simplePaginate($perPage, $columns, $pageName, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query into a cursor paginator.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $cursorName
|
||||
* @param string|null $cursor
|
||||
* @return \Illuminate\Contracts\Pagination\CursorPaginator
|
||||
*/
|
||||
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
|
||||
{
|
||||
$this->query->addSelect($this->shouldSelect($columns));
|
||||
|
||||
return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the select clause for the relation query.
|
||||
*
|
||||
@@ -393,7 +513,108 @@ class HasManyThrough extends Relation
|
||||
$columns = [$this->related->getTable().'.*'];
|
||||
}
|
||||
|
||||
return array_merge($columns, [$this->getQualifiedFirstKeyName()]);
|
||||
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 ??= $this->getRelated()->getQualifiedKeyName();
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunks of the given size.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazy($chunkSize = 1000)
|
||||
{
|
||||
return $this->prepareQueryBuilder()->lazy($chunkSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
|
||||
{
|
||||
$column ??= $this->getRelated()->getQualifiedKeyName();
|
||||
|
||||
$alias ??= $this->getRelated()->getKeyName();
|
||||
|
||||
return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,21 +627,67 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
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->getExistenceCompareKey(), '=', $this->getQualifiedFirstKeyName()
|
||||
$this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key for comparing against the parent key in "has" query.
|
||||
* Add the constraints for a relationship query on the same table.
|
||||
*
|
||||
* @return string
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getExistenceCompareKey()
|
||||
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
return $this->farParent->getQualifiedKeyName();
|
||||
$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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,13 +701,13 @@ class HasManyThrough extends Relation
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified foreign key on the related model.
|
||||
* Get the foreign key on the "through" model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedForeignKeyName()
|
||||
public function getFirstKeyName()
|
||||
{
|
||||
return $this->related->getTable().'.'.$this->secondKey;
|
||||
return $this->firstKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,6 +717,56 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function getQualifiedFirstKeyName()
|
||||
{
|
||||
return $this->throughParent->getTable().'.'.$this->firstKey;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Contracts\Database\Eloquent\SupportsPartialRelations;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\CanBeOneOfMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesRelatedModels;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
|
||||
class HasOne extends HasOneOrMany
|
||||
class HasOne extends HasOneOrMany implements SupportsPartialRelations
|
||||
{
|
||||
use SupportsDefaultModels;
|
||||
use ComparesRelatedModels, CanBeOneOfMany, SupportsDefaultModels;
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
@@ -17,13 +22,17 @@ class HasOne extends HasOneOrMany
|
||||
*/
|
||||
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 array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
@@ -49,6 +58,59 @@ class HasOne extends HasOneOrMany
|
||||
return $this->matchOne($models, $results, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = ['*'])
|
||||
{
|
||||
if ($this->isOneOfMany()) {
|
||||
$this->mergeOneOfManyJoinsTo($query);
|
||||
}
|
||||
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add constraints for inner join subselect for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return void
|
||||
*/
|
||||
public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null)
|
||||
{
|
||||
$query->addSelect($this->foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should be selected by the one of many subquery.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function getOneOfManySubQuerySelectColumns()
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join query constraints for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\JoinClause $join
|
||||
* @return void
|
||||
*/
|
||||
public function addOneOfManyJoinSubQueryConstraints(JoinClause $join)
|
||||
{
|
||||
$join->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
@@ -61,4 +123,15 @@ class HasOne extends HasOneOrMany
|
||||
$this->getForeignKeyName(), $parent->{$this->localKey}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelatedKeyFrom(Model $model)
|
||||
{
|
||||
return $model->getAttribute($this->getForeignKeyName());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user