diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 7e54581c2a..0000000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-/.github export-ignore
-.gitattributes export-ignore
diff --git a/.github/workflows/close-pull-request.yml b/.github/workflows/close-pull-request.yml
deleted file mode 100644
index 6cbfcf0671..0000000000
--- a/.github/workflows/close-pull-request.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Close Pull Request
-
-on:
- pull_request_target:
- types: [opened]
-
-jobs:
- run:
- runs-on: ubuntu-24.04
- steps:
- - uses: superbrothers/close-pull-request@v3
- with:
- comment: "Thank you for your pull request. However, you have submitted this PR on the Illuminate organization which is a read-only sub split of `laravel/framework`. Please submit your PR on the https://github.com/laravel/framework repository.
Thanks!"
diff --git a/Capsule/Manager.php b/Capsule/Manager.php
index ddcc85dcf7..1a14401973 100755
--- a/Capsule/Manager.php
+++ b/Capsule/Manager.php
@@ -2,13 +2,13 @@
namespace Illuminate\Database\Capsule;
+use PDO;
use Illuminate\Container\Container;
-use Illuminate\Contracts\Events\Dispatcher;
-use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\DatabaseManager;
-use Illuminate\Database\Eloquent\Model as Eloquent;
+use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Traits\CapsuleManagerTrait;
-use PDO;
+use Illuminate\Database\Eloquent\Model as Eloquent;
+use Illuminate\Database\Connectors\ConnectionFactory;
class Manager
{
@@ -25,14 +25,15 @@ class Manager
* Create a new database capsule manager.
*
* @param \Illuminate\Container\Container|null $container
+ * @return void
*/
- public function __construct(?Container $container = null)
+ public function __construct(Container $container = null)
{
$this->setupContainer($container ?: new Container);
// Once we have the container setup, we will setup the default configuration
// options in the container "config" binding. This will make the database
- // manager work correctly out of the box without extreme configuration.
+ // manager behave correctly since all the correct binding are in place.
$this->setupDefaultConfiguration();
$this->setupManager();
@@ -65,7 +66,7 @@ protected function setupManager()
/**
* Get a connection instance from the global manager.
*
- * @param string|null $connection
+ * @param string $connection
* @return \Illuminate\Database\Connection
*/
public static function connection($connection = null)
@@ -76,20 +77,19 @@ public static function connection($connection = null)
/**
* Get a fluent query builder instance.
*
- * @param \Closure|\Illuminate\Database\Query\Builder|string $table
- * @param string|null $as
- * @param string|null $connection
+ * @param string $table
+ * @param string $connection
* @return \Illuminate\Database\Query\Builder
*/
- public static function table($table, $as = null, $connection = null)
+ public static function table($table, $connection = null)
{
- return static::$instance->connection($connection)->table($table, $as);
+ return static::$instance->connection($connection)->table($table);
}
/**
* Get a schema builder instance.
*
- * @param string|null $connection
+ * @param string $connection
* @return \Illuminate\Database\Schema\Builder
*/
public static function schema($connection = null)
@@ -100,7 +100,7 @@ public static function schema($connection = null)
/**
* Get a registered connection instance.
*
- * @param string|null $name
+ * @param string $name
* @return \Illuminate\Database\Connection
*/
public function getConnection($name = null)
@@ -111,7 +111,7 @@ public function getConnection($name = null)
/**
* Register a connection with the manager.
*
- * @param array $config
+ * @param array $config
* @param string $name
* @return void
*/
@@ -135,7 +135,7 @@ public function bootEloquent()
// If we have an event dispatcher instance, we will go ahead and register it
// with the Eloquent ORM, allowing for model callbacks while creating and
- // updating "model" instances; however, it is not necessary to operate.
+ // updating "model" instances; however, if it not necessary to operate.
if ($dispatcher = $this->getEventDispatcher()) {
Eloquent::setEventDispatcher($dispatcher);
}
@@ -191,11 +191,11 @@ public function setEventDispatcher(Dispatcher $dispatcher)
* Dynamically pass methods to the default connection.
*
* @param string $method
- * @param array $parameters
+ * @param array $parameters
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
- return static::connection()->$method(...$parameters);
+ return call_user_func_array([static::connection(), $method], $parameters);
}
}
diff --git a/ClassMorphViolationException.php b/ClassMorphViolationException.php
deleted file mode 100644
index 6594d2d902..0000000000
--- a/ClassMorphViolationException.php
+++ /dev/null
@@ -1,29 +0,0 @@
-model = $class;
- }
-}
diff --git a/Concerns/BuildsQueries.php b/Concerns/BuildsQueries.php
deleted file mode 100644
index 537594e085..0000000000
--- a/Concerns/BuildsQueries.php
+++ /dev/null
@@ -1,615 +0,0 @@
-, int): mixed $callback
- * @return bool
- */
- public function chunk($count, callable $callback)
- {
- $this->enforceOrderBy();
-
- $skip = $this->getOffset();
- $remaining = $this->getLimit();
-
- $page = 1;
-
- do {
- $offset = (($page - 1) * $count) + (int) $skip;
-
- $limit = is_null($remaining) ? $count : min($count, $remaining);
-
- if ($limit == 0) {
- break;
- }
-
- $results = $this->offset($offset)->limit($limit)->get();
-
- $countResults = $results->count();
-
- if ($countResults == 0) {
- break;
- }
-
- if (! is_null($remaining)) {
- $remaining = max($remaining - $countResults, 0);
- }
-
- if ($callback($results, $page) === false) {
- return false;
- }
-
- unset($results);
-
- $page++;
- } while ($countResults == $count);
-
- return true;
- }
-
- /**
- * Run a map over each item while chunking.
- *
- * @template TReturn
- *
- * @param callable(TValue): TReturn $callback
- * @param int $count
- * @return \Illuminate\Support\Collection
- */
- public function chunkMap(callable $callback, $count = 1000)
- {
- $collection = new Collection;
-
- $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(TValue, int): mixed $callback
- * @param int $count
- * @return bool
- *
- * @throws \RuntimeException
- */
- 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;
- }
- }
- });
- }
-
- /**
- * Chunk the results of a query by comparing IDs.
- *
- * @param int $count
- * @param callable(\Illuminate\Support\Collection, int): mixed $callback
- * @param string|null $column
- * @param string|null $alias
- * @return bool
- */
- public function chunkById($count, callable $callback, $column = null, $alias = null)
- {
- return $this->orderedChunkById($count, $callback, $column, $alias);
- }
-
- /**
- * Chunk the results of a query by comparing IDs in descending order.
- *
- * @param int $count
- * @param callable(\Illuminate\Support\Collection, int): mixed $callback
- * @param string|null $column
- * @param string|null $alias
- * @return bool
- */
- public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null)
- {
- return $this->orderedChunkById($count, $callback, $column, $alias, descending: true);
- }
-
- /**
- * Chunk the results of a query by comparing IDs in a given order.
- *
- * @param int $count
- * @param callable(\Illuminate\Support\Collection, int): mixed $callback
- * @param string|null $column
- * @param string|null $alias
- * @param bool $descending
- * @return bool
- *
- * @throws \RuntimeException
- */
- public function orderedChunkById($count, callable $callback, $column = null, $alias = null, $descending = false)
- {
- $column ??= $this->defaultKeyName();
- $alias ??= $column;
- $lastId = null;
- $skip = $this->getOffset();
- $remaining = $this->getLimit();
-
- $page = 1;
-
- do {
- $clone = clone $this;
-
- if ($skip && $page > 1) {
- $clone->offset(0);
- }
-
- $limit = is_null($remaining) ? $count : min($count, $remaining);
-
- if ($limit == 0) {
- break;
- }
-
- // 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.
- if ($descending) {
- $results = $clone->forPageBeforeId($limit, $lastId, $column)->get();
- } else {
- $results = $clone->forPageAfterId($limit, $lastId, $column)->get();
- }
-
- $countResults = $results->count();
-
- if ($countResults == 0) {
- break;
- }
-
- if (! is_null($remaining)) {
- $remaining = max($remaining - $countResults, 0);
- }
-
- // 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(TValue, int): mixed $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 new LazyCollection(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
- * @throws \RuntimeException
- */
- 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 new LazyCollection(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};
-
- if ($lastId === null) {
- throw new RuntimeException("The lazyById operation was aborted because the [{$alias}] column is not present in the query result.");
- }
- }
- });
- }
-
- /**
- * Execute the query and get the first result.
- *
- * @param array|string $columns
- * @return TValue|null
- */
- public function first($columns = ['*'])
- {
- return $this->limit(1)->get($columns)->first();
- }
-
- /**
- * Execute the query and get the first result or throw an exception.
- *
- * @param array|string $columns
- * @param string|null $message
- * @return TValue
- *
- * @throws \Illuminate\Database\RecordNotFoundException
- */
- public function firstOrFail($columns = ['*'], $message = null)
- {
- if (! is_null($result = $this->first($columns))) {
- return $result;
- }
-
- throw new RecordNotFoundException($message ?: 'No record found for the given query.');
- }
-
- /**
- * Execute the query and get the first result if it's the sole matching record.
- *
- * @param array|string $columns
- * @return TValue
- *
- * @throws \Illuminate\Database\RecordsNotFoundException
- * @throws \Illuminate\Database\MultipleRecordsFoundException
- */
- public function sole($columns = ['*'])
- {
- $result = $this->limit(2)->get($columns);
-
- $count = $result->count();
-
- if ($count === 0) {
- throw new RecordsNotFoundException;
- }
-
- if ($count > 1) {
- throw new MultipleRecordsFoundException($count);
- }
-
- return $result->first();
- }
-
- /**
- * Paginate the given query using a cursor paginator.
- *
- * @param int $perPage
- * @param array|string $columns
- * @param string $cursorName
- * @param \Illuminate\Pagination\Cursor|string|null $cursor
- * @return \Illuminate\Contracts\Pagination\CursorPaginator
- */
- protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
- {
- if (! $cursor instanceof Cursor) {
- $cursor = is_string($cursor)
- ? Cursor::fromEncoded($cursor)
- : CursorPaginator::resolveCurrentCursor($cursorName, $cursor);
- }
-
- $orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems());
-
- if (! is_null($cursor)) {
- // Reset the union bindings so we can add the cursor where in the correct position...
- $this->setBindings([], 'union');
-
- $addCursorConditions = function (self $builder, $previousColumn, $originalColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
- $unionBuilders = $builder->getUnionBuilders();
-
- 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($unionBuilder, $previousColumn),
- '=',
- $cursor->parameter($previousColumn)
- );
-
- $this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
- });
- }
-
- $builder->where(function (self $secondBuilder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
- ['column' => $column, 'direction' => $direction] = $orders[$i];
-
- $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column);
-
- $secondBuilder->where(
- Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn,
- $direction === 'asc' ? '>' : '<',
- $cursor->parameter($column)
- );
-
- if ($i < $orders->count() - 1) {
- $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $originalColumn, $i) {
- $addCursorConditions($thirdBuilder, $column, $originalColumn, $i + 1);
- });
- }
-
- $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
- $unionWheres = $unionBuilder->getRawBindings()['where'];
-
- $originalColumn = $this->getOriginalColumnNameForCursorPagination($unionBuilder, $column);
- $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $originalColumn, $unionWheres) {
- $unionBuilder->where(
- $originalColumn,
- $direction === 'asc' ? '>' : '<',
- $cursor->parameter($column)
- );
-
- if ($i < $orders->count() - 1) {
- $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $originalColumn, $i) {
- $addCursorConditions($fourthBuilder, $column, $originalColumn, $i + 1);
- });
- }
-
- $this->addBinding($unionWheres, 'union');
- $this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
- });
- });
- });
- };
-
- $addCursorConditions($this, null, 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()->getColumns() : $builder->getColumns();
-
- 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;
- }
-
- /**
- * Create a new length-aware paginator instance.
- *
- * @param \Illuminate\Support\Collection $items
- * @param int $total
- * @param int $perPage
- * @param int $currentPage
- * @param array $options
- * @return \Illuminate\Pagination\LengthAwarePaginator
- */
- protected function paginator($items, $total, $perPage, $currentPage, $options)
- {
- return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
- 'items', 'total', 'perPage', 'currentPage', 'options'
- ));
- }
-
- /**
- * Create a new simple paginator instance.
- *
- * @param \Illuminate\Support\Collection $items
- * @param int $perPage
- * @param int $currentPage
- * @param array $options
- * @return \Illuminate\Pagination\Paginator
- */
- protected function simplePaginator($items, $perPage, $currentPage, $options)
- {
- return Container::getInstance()->makeWith(Paginator::class, compact(
- '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 and then return it.
- *
- * @param callable($this): mixed $callback
- * @return $this
- */
- public function tap($callback)
- {
- $callback($this);
-
- return $this;
- }
-
- /**
- * Pass the query to a given callback and return the result.
- *
- * @template TReturn
- *
- * @param (callable($this): TReturn) $callback
- * @return (TReturn is null|void ? $this : TReturn)
- */
- public function pipe($callback)
- {
- return $callback($this) ?? $this;
- }
-}
diff --git a/Concerns/BuildsWhereDateClauses.php b/Concerns/BuildsWhereDateClauses.php
deleted file mode 100644
index 06da844273..0000000000
--- a/Concerns/BuildsWhereDateClauses.php
+++ /dev/null
@@ -1,249 +0,0 @@
-wherePastOrFuture($columns, '<', 'and');
- }
-
- /**
- * Add a where clause to determine if a "date" column is in the past or now to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereNowOrPast($columns)
- {
- return $this->wherePastOrFuture($columns, '<=', 'and');
- }
-
- /**
- * Add an "or where" clause to determine if a "date" column is in the past to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWherePast($columns)
- {
- return $this->wherePastOrFuture($columns, '<', 'or');
- }
-
- /**
- * Add a where clause to determine if a "date" column is in the past or now to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereNowOrPast($columns)
- {
- return $this->wherePastOrFuture($columns, '<=', 'or');
- }
-
- /**
- * Add a where clause to determine if a "date" column is in the future to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereFuture($columns)
- {
- return $this->wherePastOrFuture($columns, '>', 'and');
- }
-
- /**
- * Add a where clause to determine if a "date" column is in the future or now to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereNowOrFuture($columns)
- {
- return $this->wherePastOrFuture($columns, '>=', 'and');
- }
-
- /**
- * Add an "or where" clause to determine if a "date" column is in the future to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereFuture($columns)
- {
- return $this->wherePastOrFuture($columns, '>', 'or');
- }
-
- /**
- * Add an "or where" clause to determine if a "date" column is in the future or now to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereNowOrFuture($columns)
- {
- return $this->wherePastOrFuture($columns, '>=', 'or');
- }
-
- /**
- * Add an "where" clause to determine if a "date" column is in the past or future.
- *
- * @param array|string $columns
- * @param string $operator
- * @param string $boolean
- * @return $this
- */
- protected function wherePastOrFuture($columns, $operator, $boolean)
- {
- $type = 'Basic';
- $value = Carbon::now();
-
- foreach (Arr::wrap($columns) as $column) {
- $this->wheres[] = compact('type', 'column', 'boolean', 'operator', 'value');
-
- $this->addBinding($value);
- }
-
- return $this;
- }
-
- /**
- * Add a "where date" clause to determine if a "date" column is today to the query.
- *
- * @param array|string $columns
- * @param string $boolean
- * @return $this
- */
- public function whereToday($columns, $boolean = 'and')
- {
- return $this->whereTodayBeforeOrAfter($columns, '=', $boolean);
- }
-
- /**
- * Add a "where date" clause to determine if a "date" column is before today.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereBeforeToday($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '<', 'and');
- }
-
- /**
- * Add a "where date" clause to determine if a "date" column is today or before to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereTodayOrBefore($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '<=', 'and');
- }
-
- /**
- * Add a "where date" clause to determine if a "date" column is after today.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereAfterToday($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '>', 'and');
- }
-
- /**
- * Add a "where date" clause to determine if a "date" column is today or after to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function whereTodayOrAfter($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '>=', 'and');
- }
-
- /**
- * Add an "or where date" clause to determine if a "date" column is today to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereToday($columns)
- {
- return $this->whereToday($columns, 'or');
- }
-
- /**
- * Add an "or where date" clause to determine if a "date" column is before today.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereBeforeToday($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '<', 'or');
- }
-
- /**
- * Add an "or where date" clause to determine if a "date" column is today or before to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereTodayOrBefore($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '<=', 'or');
- }
-
- /**
- * Add an "or where date" clause to determine if a "date" column is after today.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereAfterToday($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '>', 'or');
- }
-
- /**
- * Add an "or where date" clause to determine if a "date" column is today or after to the query.
- *
- * @param array|string $columns
- * @return $this
- */
- public function orWhereTodayOrAfter($columns)
- {
- return $this->whereTodayBeforeOrAfter($columns, '>=', 'or');
- }
-
- /**
- * Add a "where date" clause to determine if a "date" column is today or after to the query.
- *
- * @param array|string $columns
- * @param string $operator
- * @param string $boolean
- * @return $this
- */
- protected function whereTodayBeforeOrAfter($columns, $operator, $boolean)
- {
- $value = Carbon::today()->format('Y-m-d');
-
- foreach (Arr::wrap($columns) as $column) {
- $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
- }
-
- return $this;
- }
-}
diff --git a/Concerns/CompilesJsonPaths.php b/Concerns/CompilesJsonPaths.php
deleted file mode 100644
index fb62914374..0000000000
--- a/Concerns/CompilesJsonPaths.php
+++ /dev/null
@@ -1,65 +0,0 @@
-', $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 = (new Collection(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.'"';
- }
-}
diff --git a/Concerns/ExplainsQueries.php b/Concerns/ExplainsQueries.php
deleted file mode 100644
index 7168de1e55..0000000000
--- a/Concerns/ExplainsQueries.php
+++ /dev/null
@@ -1,24 +0,0 @@
-toSql();
-
- $bindings = $this->getBindings();
-
- $explanation = $this->getConnection()->select('EXPLAIN '.$sql, $bindings);
-
- return new Collection($explanation);
- }
-}
diff --git a/Concerns/ManagesTransactions.php b/Concerns/ManagesTransactions.php
deleted file mode 100644
index 9874727d26..0000000000
--- a/Concerns/ManagesTransactions.php
+++ /dev/null
@@ -1,373 +0,0 @@
-beginTransaction();
-
- // We'll simply execute the given callback within a try / catch block and if we
- // 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 {
- $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 exception.
- catch (Throwable $e) {
- $this->handleTransactionException(
- $e, $currentAttempt, $attempts
- );
-
- continue;
- }
-
- $levelBeingCommitted = $this->transactions;
-
- try {
- if ($this->transactions == 1) {
- $this->fireConnectionEvent('committing');
- $this->getPdo()->commit();
- }
-
- $this->transactions = max(0, $this->transactions - 1);
- } catch (Throwable $e) {
- $this->handleCommitTransactionException(
- $e, $currentAttempt, $attempts
- );
-
- continue;
- }
-
- $this->transactionsManager?->commit(
- $this->getName(),
- $levelBeingCommitted,
- $this->transactions
- );
-
- $this->fireConnectionEvent('committed');
-
- return $callbackResult;
- }
- }
-
- /**
- * Handle an exception encountered when running a transacted statement.
- *
- * @param \Throwable $e
- * @param int $currentAttempt
- * @param int $maxAttempts
- * @return void
- *
- * @throws \Throwable
- */
- 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->causedByConcurrencyError($e) &&
- $this->transactions > 1) {
- $this->transactions--;
-
- $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
- // can check if we have exceeded the maximum attempt count for this and
- // if we haven't we will return and try this query again in our loop.
- $this->rollBack();
-
- if ($this->causedByConcurrencyError($e) &&
- $currentAttempt < $maxAttempts) {
- return;
- }
-
- throw $e;
- }
-
- /**
- * Start a new database transaction.
- *
- * @return void
- *
- * @throws \Throwable
- */
- public function beginTransaction()
- {
- foreach ($this->beforeStartingTransaction as $callback) {
- $callback($this);
- }
-
- $this->createTransaction();
-
- $this->transactions++;
-
- $this->transactionsManager?->begin(
- $this->getName(), $this->transactions
- );
-
- $this->fireConnectionEvent('beganTransaction');
- }
-
- /**
- * Create a transaction within the database.
- *
- * @return void
- *
- * @throws \Throwable
- */
- protected function createTransaction()
- {
- if ($this->transactions == 0) {
- $this->reconnectIfMissingConnection();
-
- try {
- $this->executeBeginTransactionStatement();
- } catch (Throwable $e) {
- $this->handleBeginTransactionException($e);
- }
- } elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
- $this->createSavepoint();
- }
- }
-
- /**
- * Create a save point within the database.
- *
- * @return void
- *
- * @throws \Throwable
- */
- protected function createSavepoint()
- {
- $this->getPdo()->exec(
- $this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
- );
- }
-
- /**
- * Handle an exception from a transaction beginning.
- *
- * @param \Throwable $e
- * @return void
- *
- * @throws \Throwable
- */
- protected function handleBeginTransactionException(Throwable $e)
- {
- if ($this->causedByLostConnection($e)) {
- $this->reconnect();
-
- $this->executeBeginTransactionStatement();
- } else {
- throw $e;
- }
- }
-
- /**
- * Commit the active database transaction.
- *
- * @return void
- *
- * @throws \Throwable
- */
- public function commit()
- {
- if ($this->transactionLevel() == 1) {
- $this->fireConnectionEvent('committing');
- $this->getPdo()->commit();
- }
-
- [$levelBeingCommitted, $this->transactions] = [
- $this->transactions,
- max(0, $this->transactions - 1),
- ];
-
- $this->transactionsManager?->commit(
- $this->getName(), $levelBeingCommitted, $this->transactions
- );
-
- $this->fireConnectionEvent('committed');
- }
-
- /**
- * 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)
- {
- // We allow developers to rollback to a certain transaction level. We will verify
- // that this given transaction level is valid before attempting to rollback to
- // that level. If it's not we will just return out and not attempt anything.
- $toLevel = is_null($toLevel)
- ? $this->transactions - 1
- : $toLevel;
-
- if ($toLevel < 0 || $toLevel >= $this->transactions) {
- return;
- }
-
- // 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.
- try {
- $this->performRollBack($toLevel);
- } catch (Throwable $e) {
- $this->handleRollBackException($e);
- }
-
- $this->transactions = $toLevel;
-
- $this->transactionsManager?->rollback(
- $this->getName(), $this->transactions
- );
-
- $this->fireConnectionEvent('rollingBack');
- }
-
- /**
- * Perform a rollback within the database.
- *
- * @param int $toLevel
- * @return void
- *
- * @throws \Throwable
- */
- protected function performRollBack($toLevel)
- {
- if ($toLevel == 0) {
- $pdo = $this->getPdo();
-
- if ($pdo->inTransaction()) {
- $pdo->rollBack();
- }
- } elseif ($this->queryGrammar->supportsSavepoints()) {
- $this->getPdo()->exec(
- $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
- );
- }
- }
-
- /**
- * 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.
- *
- * @return int
- */
- public function transactionLevel()
- {
- 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.');
- }
-
- /**
- * Execute the callback after a transaction rolls back.
- *
- * @param callable $callback
- * @return void
- *
- * @throws \RuntimeException
- */
- public function afterRollBack($callback)
- {
- if ($this->transactionsManager) {
- return $this->transactionsManager->addCallbackForRollback($callback);
- }
-
- throw new RuntimeException('Transactions Manager has not been set.');
- }
-}
diff --git a/Concerns/ParsesSearchPath.php b/Concerns/ParsesSearchPath.php
deleted file mode 100644
index e822c722b7..0000000000
--- a/Concerns/ParsesSearchPath.php
+++ /dev/null
@@ -1,25 +0,0 @@
-getCode() === 40001 || $e->getCode() === '40001')) {
- return true;
- }
-
- $message = $e->getMessage();
-
- return Str::contains($message, [
- 'Deadlock found when trying to get lock',
- 'deadlock detected',
- 'The database file is locked',
- 'database is locked',
- 'database table is locked',
- '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',
- 'Record has changed since last read in table',
- ]);
- }
-}
diff --git a/ConfigurationUrlParser.php b/ConfigurationUrlParser.php
deleted file mode 100644
index bc7c624a28..0000000000
--- a/ConfigurationUrlParser.php
+++ /dev/null
@@ -1,10 +0,0 @@
-): mixed)}[]
- */
- protected $queryDurationHandlers = [];
-
/**
* Indicates if the connection is in a "dry run".
*
@@ -190,40 +121,41 @@ class Connection implements ConnectionInterface
protected $pretending = false;
/**
- * All of the callbacks that should be invoked before a transaction is started.
+ * The name of the connected database.
*
- * @var \Closure[]
+ * @var string
*/
- protected $beforeStartingTransaction = [];
+ protected $database;
/**
- * All of the callbacks that should be invoked before a query is executed.
+ * The instance of Doctrine connection.
*
- * @var (\Closure(string, array, \Illuminate\Database\Connection): mixed)[]
+ * @var \Doctrine\DBAL\Connection
*/
- protected $beforeExecutingCallbacks = [];
+ protected $doctrineConnection;
/**
- * The connection resolvers.
+ * The table prefix for the connection.
*
- * @var \Closure[]
+ * @var string
*/
- protected static $resolvers = [];
+ protected $tablePrefix = '';
/**
- * The last retrieved PDO read / write type.
+ * The database connection configuration options.
*
- * @var null|'read'|'write'
+ * @var array
*/
- protected $latestPdoTypeRetrieved = null;
+ protected $config = [];
/**
* Create a new database connection instance.
*
- * @param \PDO|(\Closure(): \PDO) $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 = [])
{
@@ -263,7 +195,7 @@ public function useDefaultQueryGrammar()
*/
protected function getDefaultQueryGrammar()
{
- return new QueryGrammar($this);
+ return new QueryGrammar;
}
/**
@@ -279,7 +211,7 @@ public function useDefaultSchemaGrammar()
/**
* Get the default schema grammar instance.
*
- * @return \Illuminate\Database\Schema\Grammars\Grammar|null
+ * @return \Illuminate\Database\Schema\Grammars\Grammar
*/
protected function getDefaultSchemaGrammar()
{
@@ -323,13 +255,12 @@ public function getSchemaBuilder()
/**
* Begin a fluent query against a database table.
*
- * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Contracts\Database\Query\Expression|\UnitEnum|string $table
- * @param string|null $as
+ * @param string $table
* @return \Illuminate\Database\Query\Builder
*/
- public function table($table, $as = null)
+ public function table($table)
{
- return $this->query()->from(enum_value($table), $as);
+ return $this->query()->from($table);
}
/**
@@ -345,52 +276,35 @@ public function query()
}
/**
- * Run a select statement and return a single result.
+ * Get a new raw query expression.
*
- * @param string $query
- * @param array $bindings
- * @param bool $useReadPdo
- * @return mixed
+ * @param mixed $value
+ * @return \Illuminate\Database\Query\Expression
*/
- public function selectOne($query, $bindings = [], $useReadPdo = true)
+ public function raw($value)
{
- $records = $this->select($query, $bindings, $useReadPdo);
-
- return array_shift($records);
+ return new Expression($value);
}
/**
- * Run a select statement and return the first column of the first row.
+ * Run a select statement and return a single result.
*
* @param string $query
- * @param array $bindings
- * @param bool $useReadPdo
+ * @param array $bindings
* @return mixed
- *
- * @throws \Illuminate\Database\MultipleColumnsSelectedException
*/
- public function scalar($query, $bindings = [], $useReadPdo = true)
+ public function selectOne($query, $bindings = [])
{
- $record = $this->selectOne($query, $bindings, $useReadPdo);
-
- if (is_null($record)) {
- return null;
- }
-
- $record = (array) $record;
+ $records = $this->select($query, $bindings);
- if (count($record) > 1) {
- throw new MultipleColumnsSelectedException;
- }
-
- return array_first($record);
+ return count($records) > 0 ? reset($records) : null;
}
/**
* Run a select statement against the database.
*
* @param string $query
- * @param array $bindings
+ * @param array $bindings
* @return array
*/
public function selectFromWriteConnection($query, $bindings = [])
@@ -404,119 +318,49 @@ public function selectFromWriteConnection($query, $bindings = [])
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
- * @param array $fetchUsing
* @return array
*/
- public function select($query, $bindings = [], $useReadPdo = true, array $fetchUsing = [])
+ public function select($query, $bindings = [], $useReadPdo = true)
{
- return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo, $fetchUsing) {
- if ($this->pretending()) {
+ return $this->run($query, $bindings, function ($me, $query, $bindings) use ($useReadPdo) {
+ if ($me->pretending()) {
return [];
}
// 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)
- );
-
- $this->bindValues($statement, $this->prepareBindings($bindings));
-
- $statement->execute();
-
- return $statement->fetchAll(...$fetchUsing);
- });
- }
-
- /**
- * Run a select statement against the database and returns all of the result sets.
- *
- * @param string $query
- * @param array $bindings
- * @param bool $useReadPdo
- * @param array $fetchUsing
- * @return array
- */
- public function selectResultSets($query, $bindings = [], $useReadPdo = true, array $fetchUsing = [])
- {
- return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo, $fetchUsing) {
- if ($this->pretending()) {
- return [];
- }
-
- $statement = $this->prepared(
- $this->getPdoForSelect($useReadPdo)->prepare($query)
- );
+ $statement = $this->getPdoForSelect($useReadPdo)->prepare($query);
- $this->bindValues($statement, $this->prepareBindings($bindings));
+ $this->bindValues($statement, $me->prepareBindings($bindings));
$statement->execute();
- $sets = [];
-
- do {
- $sets[] = $statement->fetchAll(...$fetchUsing);
- } while ($statement->nextRowset());
+ $fetchArgument = $me->getFetchArgument();
- return $sets;
+ return isset($fetchArgument)
+ ? $statement->fetchAll($me->getFetchMode(), $fetchArgument, $me->getFetchConstructorArgument())
+ : $statement->fetchAll($me->getFetchMode());
});
}
/**
- * Run a select statement against the database and returns a generator.
+ * Bind values to their parameters in the given statement.
*
- * @param string $query
+ * @param \PDOStatement $statement
* @param array $bindings
- * @param bool $useReadPdo
- * @param array $fetchUsing
- * @return \Generator
+ * @return void
*/
- public function cursor($query, $bindings = [], $useReadPdo = true, array $fetchUsing = [])
+ public function bindValues($statement, $bindings)
{
- $statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
- if ($this->pretending()) {
- return [];
- }
-
- // First we will create a statement for the query. Then, we will set the fetch
- // mode and prepare the bindings for the query. Once that's done we will be
- // ready to execute the query against the database and return the cursor.
- $statement = $this->prepared($this->getPdoForSelect($useReadPdo)
- ->prepare($query));
-
- $this->bindValues(
- $statement, $this->prepareBindings($bindings)
+ foreach ($bindings as $key => $value) {
+ $statement->bindValue(
+ is_string($key) ? $key : $key + 1, $value,
+ filter_var($value, FILTER_VALIDATE_FLOAT) !== false ? PDO::PARAM_INT : PDO::PARAM_STR
);
-
- // Next, we'll execute the query against the database and return the statement
- // so we can return the cursor. The cursor will use a PHP generator to give
- // back one row at a time without using a bunch of memory to render them.
- $statement->execute();
-
- return $statement;
- });
-
- while ($record = $statement->fetch(...$fetchUsing)) {
- yield $record;
}
}
- /**
- * Configure the PDO prepared statement.
- *
- * @param \PDOStatement $statement
- * @return \PDOStatement
- */
- protected function prepared(PDOStatement $statement)
- {
- $statement->setFetchMode($this->fetchMode);
-
- $this->event(new StatementPrepared($this, $statement));
-
- return $statement;
- }
-
/**
* Get the PDO connection to use for a select query.
*
@@ -532,7 +376,7 @@ protected function getPdoForSelect($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 = [])
@@ -544,7 +388,7 @@ public function insert($query, $bindings = [])
* Run an update statement against the database.
*
* @param string $query
- * @param array $bindings
+ * @param array $bindings
* @return int
*/
public function update($query, $bindings = [])
@@ -556,7 +400,7 @@ public function update($query, $bindings = [])
* Run a delete statement against the database.
*
* @param string $query
- * @param array $bindings
+ * @param array $bindings
* @return int
*/
public function delete($query, $bindings = [])
@@ -568,21 +412,19 @@ public function delete($query, $bindings = [])
* 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 = [])
{
- return $this->run($query, $bindings, function ($query, $bindings) {
- if ($this->pretending()) {
+ return $this->run($query, $bindings, function ($me, $query, $bindings) {
+ if ($me->pretending()) {
return true;
}
$statement = $this->getPdo()->prepare($query);
- $this->bindValues($statement, $this->prepareBindings($bindings));
-
- $this->recordsHaveBeenModified();
+ $this->bindValues($statement, $me->prepareBindings($bindings));
return $statement->execute();
});
@@ -592,30 +434,26 @@ public function statement($query, $bindings = [])
* 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 = [])
{
- return $this->run($query, $bindings, function ($query, $bindings) {
- if ($this->pretending()) {
+ return $this->run($query, $bindings, function ($me, $query, $bindings) {
+ if ($me->pretending()) {
return 0;
}
// For update or delete statements, we want to get the number of rows affected
// by the statement and return that back to the developer. We'll first need
// to execute the statement and then we'll use PDO to fetch the affected.
- $statement = $this->getPdo()->prepare($query);
+ $statement = $me->getPdo()->prepare($query);
- $this->bindValues($statement, $this->prepareBindings($bindings));
+ $this->bindValues($statement, $me->prepareBindings($bindings));
$statement->execute();
- $this->recordsHaveBeenModified(
- ($count = $statement->rowCount()) > 0
- );
-
- return $count;
+ return $statement->rowCount();
});
}
@@ -627,154 +465,175 @@ public function affectingStatement($query, $bindings = [])
*/
public function unprepared($query)
{
- return $this->run($query, [], function ($query) {
- if ($this->pretending()) {
+ return $this->run($query, [], function ($me, $query) {
+ if ($me->pretending()) {
return true;
}
- $this->recordsHaveBeenModified(
- $change = $this->getPdo()->exec($query) !== false
- );
-
- return $change;
+ return (bool) $me->getPdo()->exec($query);
});
}
/**
- * Get the number of open connections for the database.
- *
- * @return int|null
- */
- public function threadCount()
- {
- $query = $this->getQueryGrammar()->compileThreadCount();
-
- return $query ? $this->scalar($query) : null;
- }
-
- /**
- * Execute the given callback in "dry run" mode.
+ * Prepare the query bindings for execution.
*
- * @param (\Closure(\Illuminate\Database\Connection): mixed) $callback
- * @return array{query: string, bindings: array, time: float|null}[]
+ * @param array $bindings
+ * @return array
*/
- public function pretend(Closure $callback)
+ public function prepareBindings(array $bindings)
{
- return $this->withFreshQueryLog(function () use ($callback) {
- $this->pretending = true;
-
- try {
- // Basically to make the database connection "pretend", we will just return
- // the default values for all the query methods, then we will return an
- // array of queries that were "executed" within the Closure callback.
- $callback($this);
+ $grammar = $this->getQueryGrammar();
- return $this->queryLog;
- } finally {
- $this->pretending = false;
+ foreach ($bindings as $key => $value) {
+ // We need to transform all instances of DateTimeInterface into the actual
+ // date string. Each query grammar maintains its own date string format
+ // 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;
}
- });
+ }
+
+ return $bindings;
}
/**
- * Execute the given callback without "pretending".
+ * Execute a Closure within a transaction.
*
* @param \Closure $callback
* @return mixed
+ *
+ * @throws \Exception|\Throwable
*/
- public function withoutPretending(Closure $callback)
+ public function transaction(Closure $callback)
{
- if (! $this->pretending) {
- return $callback();
+ $this->beginTransaction();
+
+ // We'll simply execute the given callback within a try / catch block
+ // and if we catch any exception we can rollback the transaction
+ // so that none of the changes are persisted to the database.
+ try {
+ $result = $callback($this);
+
+ $this->commit();
}
- $this->pretending = false;
+ // If we catch an exception, we will roll back so nothing gets messed
+ // up in the database. Then we'll re-throw the exception so it can
+ // be handled how the developer sees fit for their applications.
+ catch (Exception $e) {
+ $this->rollBack();
- try {
- return $callback();
- } finally {
- $this->pretending = true;
+ throw $e;
+ } catch (Throwable $e) {
+ $this->rollBack();
+
+ throw $e;
}
+
+ return $result;
}
/**
- * Execute the given callback in "dry run" mode.
+ * Start a new database transaction.
*
- * @param (\Closure(): array{query: string, bindings: array, time: float|null}[]) $callback
- * @return array{query: string, bindings: array, time: float|null}[]
+ * @return void
*/
- protected function withFreshQueryLog($callback)
+ public function beginTransaction()
{
- $loggingQueries = $this->loggingQueries;
+ ++$this->transactions;
- // First we will back up the value of the logging queries property and then
- // we'll be ready to run callbacks. This query log will also get cleared
- // so we will have a new log of all the queries that are executed now.
- $this->enableQueryLog();
+ if ($this->transactions == 1) {
+ $this->getPdo()->beginTransaction();
+ } elseif ($this->transactions > 1 && $this->queryGrammar->supportsSavepoints()) {
+ $this->getPdo()->exec(
+ $this->queryGrammar->compileSavepoint('trans'.$this->transactions)
+ );
+ }
- $this->queryLog = [];
+ $this->fireConnectionEvent('beganTransaction');
+ }
- // 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 the callback so the original callers can have the results.
- $result = $callback();
+ /**
+ * Commit the active database transaction.
+ *
+ * @return void
+ */
+ public function commit()
+ {
+ if ($this->transactions == 1) {
+ $this->getPdo()->commit();
+ }
- $this->loggingQueries = $loggingQueries;
+ --$this->transactions;
- return $result;
+ $this->fireConnectionEvent('committed');
}
/**
- * Bind values to their parameters in the given statement.
+ * Rollback the active database transaction.
*
- * @param \PDOStatement $statement
- * @param array $bindings
* @return void
*/
- public function bindValues($statement, $bindings)
+ public function rollBack()
{
- foreach ($bindings as $key => $value) {
- $statement->bindValue(
- 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
- },
+ if ($this->transactions == 1) {
+ $this->getPdo()->rollBack();
+ } elseif ($this->transactions > 1 && $this->queryGrammar->supportsSavepoints()) {
+ $this->getPdo()->exec(
+ $this->queryGrammar->compileSavepointRollBack('trans'.$this->transactions)
);
}
+
+ $this->transactions = max(0, $this->transactions - 1);
+
+ $this->fireConnectionEvent('rollingBack');
}
/**
- * Prepare the query bindings for execution.
+ * Get the number of active transactions.
*
- * @param array $bindings
+ * @return int
+ */
+ public function transactionLevel()
+ {
+ return $this->transactions;
+ }
+
+ /**
+ * Execute the given callback in "dry run" mode.
+ *
+ * @param \Closure $callback
* @return array
*/
- public function prepareBindings(array $bindings)
+ public function pretend(Closure $callback)
{
- $grammar = $this->getQueryGrammar();
+ $loggingQueries = $this->loggingQueries;
- foreach ($bindings as $key => $value) {
- // We need to transform all instances of DateTimeInterface into the actual
- // date string. Each query grammar maintains its own date string format
- // 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 (is_bool($value)) {
- $bindings[$key] = (int) $value;
- }
- }
+ $this->enableQueryLog();
- return $bindings;
+ $this->pretending = true;
+
+ $this->queryLog = [];
+
+ // Basically to make the database connection "pretend", we will just return
+ // the default values for all the query methods, then we will return an
+ // array of queries that were "executed" within the Closure callback.
+ $callback($this);
+
+ $this->pretending = false;
+
+ $this->loggingQueries = $loggingQueries;
+
+ return $this->queryLog;
}
/**
* 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
*
@@ -782,10 +641,6 @@ public function prepareBindings(array $bindings)
*/
protected function run($query, $bindings, Closure $callback)
{
- foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
- $beforeExecutingCallback($query, $bindings, $this);
- }
-
$this->reconnectIfMissingConnection();
$start = microtime(true);
@@ -796,7 +651,11 @@ protected function run($query, $bindings, Closure $callback)
try {
$result = $this->runQueryCallback($query, $bindings, $callback);
} catch (QueryException $e) {
- $result = $this->handleQueryException(
+ if ($this->transactions >= 1) {
+ throw $e;
+ }
+
+ $result = $this->tryAgainIfCausedByLostConnection(
$e, $query, $bindings, $callback
);
}
@@ -804,9 +663,9 @@ protected function run($query, $bindings, Closure $callback)
// Once we have run the query we will calculate the time that it took to run and
// then log the query, bindings, and execution time so we will report them on
// the event that the developer needs them. We'll log time in milliseconds.
- $this->logQuery(
- $query, $bindings, $this->getElapsedTime($start)
- );
+ $time = $this->getElapsedTime($start);
+
+ $this->logQuery($query, $bindings, $time);
return $result;
}
@@ -814,8 +673,8 @@ protected function run($query, $bindings, Closure $callback)
/**
* Run a SQL statement.
*
- * @param string $query
- * @param array $bindings
+ * @param string $query
+ * @param array $bindings
* @param \Closure $callback
* @return mixed
*
@@ -827,168 +686,27 @@ protected function runQueryCallback($query, $bindings, Closure $callback)
// 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 {
- return $callback($query, $bindings);
+ $result = $callback($this, $query, $bindings);
}
// If an exception occurs when attempting to run a query, we'll format the error
// message to include the bindings with SQL, which will make this exception a
// lot more helpful to the developer instead of just the database's errors.
catch (Exception $e) {
- $exceptionType = $this->isUniqueConstraintError($e)
- ? UniqueConstraintViolationException::class
- : QueryException::class;
-
- throw new $exceptionType(
- $this->getNameWithReadWriteType(),
- $query,
- $this->prepareBindings($bindings),
- $e,
- $this->getConnectionDetails(),
- $this->latestReadWriteTypeUsed(),
+ throw new QueryException(
+ $query, $this->prepareBindings($bindings), $e
);
}
- }
-
- /**
- * Determine if the given database exception was caused by a unique constraint violation.
- *
- * @param \Exception $exception
- * @return bool
- */
- protected function isUniqueConstraintError(Exception $exception)
- {
- return false;
- }
-
- /**
- * Log a query in the connection's query log.
- *
- * @param string $query
- * @param array $bindings
- * @param float|null $time
- * @return void
- */
- public function logQuery($query, $bindings, $time = null)
- {
- $this->totalQueryDuration += $time ?? 0.0;
-
- $readWriteType = $this->latestReadWriteTypeUsed();
-
- $this->event(new QueryExecuted($query, $bindings, $time, $this, $readWriteType));
-
- $query = $this->pretending === true
- ? $this->queryGrammar?->substituteBindingsIntoRawSql($query, $bindings) ?? $query
- : $query;
-
- if ($this->loggingQueries) {
- $this->queryLog[] = compact('query', 'bindings', 'time', 'readWriteType');
- }
- }
-
- /**
- * Get the elapsed time in milliseconds since a given starting point.
- *
- * @param float $start
- * @return float
- */
- protected function getElapsedTime($start)
- {
- 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(\Illuminate\Database\Connection, \Illuminate\Database\Events\QueryExecuted): mixed) $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 \Illuminate\Database\QueryException $e
- * @param string $query
- * @param array $bindings
- * @param \Closure $callback
- * @return mixed
- *
- * @throws \Illuminate\Database\QueryException
- */
- protected function handleQueryException(QueryException $e, $query, $bindings, Closure $callback)
- {
- if ($this->transactions >= 1) {
- throw $e;
- }
- return $this->tryAgainIfCausedByLostConnection(
- $e, $query, $bindings, $callback
- );
+ return $result;
}
/**
* 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
*
@@ -1005,12 +723,22 @@ protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $
throw $e;
}
+ /**
+ * Disconnect from the underlying PDO connection.
+ *
+ * @return void
+ */
+ public function disconnect()
+ {
+ $this->setPdo(null)->setReadPdo(null);
+ }
+
/**
* Reconnect to the database.
*
- * @return mixed|false
+ * @return void
*
- * @throws \Illuminate\Database\LostConnectionException
+ * @throws \LogicException
*/
public function reconnect()
{
@@ -1018,7 +746,7 @@ public function reconnect()
return call_user_func($this->reconnector, $this);
}
- throw new LostConnectionException('Lost connection and no reconnector available.');
+ throw new LogicException('Lost connection and no reconnector available.');
}
/**
@@ -1026,225 +754,130 @@ public function reconnect()
*
* @return void
*/
- public function reconnectIfMissingConnection()
+ protected function reconnectIfMissingConnection()
{
- if (is_null($this->pdo)) {
+ if (is_null($this->getPdo()) || is_null($this->getReadPdo())) {
$this->reconnect();
}
}
/**
- * Disconnect from the underlying PDO connection.
+ * Log a query in the connection's query log.
*
+ * @param string $query
+ * @param array $bindings
+ * @param float|null $time
* @return void
*/
- public function disconnect()
- {
- $this->setPdo(null)->setReadPdo(null);
- }
-
- /**
- * Register a hook to be run just before a database transaction is started.
- *
- * @param \Closure $callback
- * @return $this
- */
- public function beforeStartingTransaction(Closure $callback)
- {
- $this->beforeStartingTransaction[] = $callback;
-
- return $this;
- }
-
- /**
- * Register a hook to be run just before a database query is executed.
- *
- * @param \Closure $callback
- * @return $this
- */
- public function beforeExecuting(Closure $callback)
+ public function logQuery($query, $bindings, $time = null)
{
- $this->beforeExecutingCallbacks[] = $callback;
+ if (isset($this->events)) {
+ $this->events->fire(new Events\QueryExecuted(
+ $query, $bindings, $time, $this
+ ));
+ }
- return $this;
+ if ($this->loggingQueries) {
+ $this->queryLog[] = compact('query', 'bindings', 'time');
+ }
}
/**
* Register a database query listener with the connection.
*
- * @param \Closure(\Illuminate\Database\Events\QueryExecuted) $callback
+ * @param \Closure $callback
* @return void
*/
public function listen(Closure $callback)
{
- $this->events?->listen(Events\QueryExecuted::class, $callback);
+ if (isset($this->events)) {
+ $this->events->listen(Events\QueryExecuted::class, $callback);
+ }
}
/**
* Fire an event for this connection.
*
* @param string $event
- * @return array|null
- */
- protected function fireConnectionEvent($event)
- {
- return $this->events?->dispatch(match ($event) {
- 'beganTransaction' => new TransactionBeginning($this),
- 'committed' => new TransactionCommitted($this),
- 'committing' => new TransactionCommitting($this),
- 'rollingBack' => new TransactionRolledBack($this),
- default => null,
- });
- }
-
- /**
- * Fire the given event if possible.
- *
- * @param mixed $event
* @return void
*/
- protected function event($event)
- {
- $this->events?->dispatch($event);
- }
-
- /**
- * Get a new raw query expression.
- *
- * @param mixed $value
- * @return \Illuminate\Contracts\Database\Query\Expression
- */
- public function raw($value)
+ protected function fireConnectionEvent($event)
{
- return new Expression($value);
- }
-
- /**
- * Escape a value for safe SQL embedding.
- *
- * @param string|float|int|bool|null $value
- * @param bool $binary
- * @return string
- *
- * @throws \RuntimeException
- */
- public function escape($value, $binary = false)
- {
- if ($value === null) {
- return 'null';
- } elseif ($binary) {
- return $this->escapeBinary($value);
- } elseif (is_int($value) || is_float($value)) {
- return (string) $value;
- } elseif (is_bool($value)) {
- return $this->escapeBool($value);
- } elseif (is_array($value)) {
- throw new RuntimeException('The database connection does not support escaping arrays.');
- } else {
- if (str_contains($value, "\00")) {
- throw new RuntimeException('Strings with null bytes cannot be escaped. Use the binary escape option.');
- }
-
- if (preg_match('//u', $value) === false) {
- throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.');
- }
+ if (! isset($this->events)) {
+ return;
+ }
- return $this->escapeString($value);
+ switch ($event) {
+ case 'beganTransaction':
+ return $this->events->fire(new Events\TransactionBeginning($this));
+ case 'committed':
+ return $this->events->fire(new Events\TransactionCommitted($this));
+ case 'rollingBack':
+ return $this->events->fire(new Events\TransactionRolledBack($this));
}
}
/**
- * Escape a string value for safe SQL embedding.
+ * Get the elapsed time since a given starting point.
*
- * @param string $value
- * @return string
+ * @param int $start
+ * @return float
*/
- protected function escapeString($value)
+ protected function getElapsedTime($start)
{
- return $this->getReadPdo()->quote($value);
+ return round((microtime(true) - $start) * 1000, 2);
}
/**
- * Escape a boolean value for safe SQL embedding.
+ * Is Doctrine available?
*
- * @param bool $value
- * @return string
+ * @return bool
*/
- protected function escapeBool($value)
+ public function isDoctrineAvailable()
{
- return $value ? '1' : '0';
+ return class_exists('Doctrine\DBAL\Connection');
}
/**
- * Escape a binary value for safe SQL embedding.
+ * Get a Doctrine Schema Column instance.
*
- * @param string $value
- * @return string
- *
- * @throws \RuntimeException
+ * @param string $table
+ * @param string $column
+ * @return \Doctrine\DBAL\Schema\Column
*/
- protected function escapeBinary($value)
+ public function getDoctrineColumn($table, $column)
{
- throw new RuntimeException('The database connection does not support escaping binary values.');
- }
+ $schema = $this->getDoctrineSchemaManager();
- /**
- * Determine if the database connection has modified any database records.
- *
- * @return bool
- */
- public function hasModifiedRecords()
- {
- return $this->recordsModified;
+ return $schema->listTableDetails($table)->getColumn($column);
}
/**
- * Indicate if any records have been modified.
+ * Get the Doctrine DBAL schema manager for the connection.
*
- * @param bool $value
- * @return void
+ * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
*/
- public function recordsHaveBeenModified($value = true)
+ public function getDoctrineSchemaManager()
{
- if (! $this->recordsModified) {
- $this->recordsModified = $value;
- }
+ return $this->getDoctrineDriver()->getSchemaManager($this->getDoctrineConnection());
}
/**
- * Set the record modification state.
+ * Get the Doctrine DBAL database connection instance.
*
- * @param bool $value
- * @return $this
+ * @return \Doctrine\DBAL\Connection
*/
- public function setRecordModificationState(bool $value)
+ public function getDoctrineConnection()
{
- $this->recordsModified = $value;
+ if (is_null($this->doctrineConnection)) {
+ $driver = $this->getDoctrineDriver();
- return $this;
- }
+ $data = ['pdo' => $this->getPdo(), 'dbname' => $this->getConfig('database')];
- /**
- * 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;
+ $this->doctrineConnection = new DoctrineConnection($data, $driver);
+ }
- return $this;
+ return $this->doctrineConnection;
}
/**
@@ -1254,8 +887,6 @@ public function useWriteConnectionWhenReading($value = true)
*/
public function getPdo()
{
- $this->latestPdoTypeRetrieved = 'write';
-
if ($this->pdo instanceof Closure) {
return $this->pdo = call_user_func($this->pdo);
}
@@ -1263,16 +894,6 @@ public function getPdo()
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.
*
@@ -1280,43 +901,26 @@ public function getRawPdo()
*/
public function getReadPdo()
{
- if ($this->transactions > 0) {
- return $this->getPdo();
- }
-
- if ($this->readOnWriteConnection ||
- ($this->recordsModified && $this->getConfig('sticky'))) {
+ if ($this->transactions >= 1) {
return $this->getPdo();
}
- $this->latestPdoTypeRetrieved = 'read';
-
- if ($this->readPdo instanceof Closure) {
- return $this->readPdo = call_user_func($this->readPdo);
- }
-
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.
*
- * @param \PDO|\Closure|null $pdo
+ * @param \PDO|null $pdo
* @return $this
+ *
+ * @throws \RuntimeException
*/
public function setPdo($pdo)
{
- $this->transactions = 0;
+ if ($this->transactions >= 1) {
+ throw new RuntimeException("Can't swap PDO instance while within transaction.");
+ }
$this->pdo = $pdo;
@@ -1326,7 +930,7 @@ public function setPdo($pdo)
/**
* Set the PDO connection used for reading.
*
- * @param \PDO|\Closure|null $pdo
+ * @param \PDO|null $pdo
* @return $this
*/
public function setReadPdo($pdo)
@@ -1336,23 +940,10 @@ public function setReadPdo($pdo)
return $this;
}
- /**
- * Set the read PDO connection configuration.
- *
- * @param array $config
- * @return $this
- */
- public function setReadPdoConfig(array $config)
- {
- $this->readPdoConfig = $config;
-
- return $this;
- }
-
/**
* Set the reconnect instance on the connection.
*
- * @param (callable(\Illuminate\Database\Connection): mixed) $reconnector
+ * @param callable $reconnector
* @return $this
*/
public function setReconnector(callable $reconnector)
@@ -1372,50 +963,17 @@ public function getName()
return $this->getConfig('name');
}
- /**
- * Get the database connection with its read / write type.
- *
- * @return string|null
- */
- public function getNameWithReadWriteType()
- {
- $name = $this->getName().($this->readWriteType ? '::'.$this->readWriteType : '');
-
- return empty($name) ? null : $name;
- }
-
/**
* Get an option from the configuration options.
*
- * @param string|null $option
+ * @param string $option
* @return mixed
*/
- public function getConfig($option = null)
+ public function getConfig($option)
{
return Arr::get($this->config, $option);
}
- /**
- * Get the basic connection information as an array for debugging.
- *
- * @return array
- */
- protected function getConnectionDetails()
- {
- $config = $this->latestReadWriteTypeUsed() === 'read'
- ? $this->readPdoConfig
- : $this->config;
-
- return [
- 'driver' => $this->getDriverName(),
- 'name' => $this->getNameWithReadWriteType(),
- 'host' => $config['host'] ?? null,
- 'port' => $config['port'] ?? null,
- 'database' => $config['database'] ?? null,
- 'unix_socket' => $config['unix_socket'] ?? null,
- ];
- }
-
/**
* Get the PDO driver name.
*
@@ -1426,16 +984,6 @@ public function getDriverName()
return $this->getConfig('driver');
}
- /**
- * Get a human-readable name for the given connection driver.
- *
- * @return string
- */
- public function getDriverTitle()
- {
- return $this->getDriverName();
- }
-
/**
* Get the query grammar used by the connection.
*
@@ -1450,13 +998,11 @@ public function getQueryGrammar()
* Set the query grammar used by the connection.
*
* @param \Illuminate\Database\Query\Grammars\Grammar $grammar
- * @return $this
+ * @return void
*/
public function setQueryGrammar(Query\Grammars\Grammar $grammar)
{
$this->queryGrammar = $grammar;
-
- return $this;
}
/**
@@ -1473,13 +1019,11 @@ public function getSchemaGrammar()
* Set the schema grammar used by the connection.
*
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
- * @return $this
+ * @return void
*/
public function setSchemaGrammar(Schema\Grammars\Grammar $grammar)
{
$this->schemaGrammar = $grammar;
-
- return $this;
}
/**
@@ -1496,19 +1040,17 @@ public function getPostProcessor()
* Set the query post processor used by the connection.
*
* @param \Illuminate\Database\Query\Processors\Processor $processor
- * @return $this
+ * @return void
*/
public function setPostProcessor(Processor $processor)
{
$this->postProcessor = $processor;
-
- return $this;
}
/**
* Get the event dispatcher used by the connection.
*
- * @return \Illuminate\Contracts\Events\Dispatcher|null
+ * @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getEventDispatcher()
{
@@ -1519,94 +1061,78 @@ public function getEventDispatcher()
* Set the event dispatcher instance on the connection.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
- * @return $this
+ * @return void
*/
public function setEventDispatcher(Dispatcher $events)
{
$this->events = $events;
-
- return $this;
}
/**
- * Unset the event dispatcher for this connection.
+ * Determine if the connection in a "dry run".
*
- * @return void
+ * @return bool
*/
- public function unsetEventDispatcher()
+ public function pretending()
{
- $this->events = null;
+ return $this->pretending === true;
}
/**
- * Run the statement to start a new transaction.
+ * Get the default fetch mode for the connection.
*
- * @return void
+ * @return int
*/
- protected function executeBeginTransactionStatement()
+ public function getFetchMode()
{
- $this->getPdo()->beginTransaction();
+ return $this->fetchMode;
}
/**
- * Set the transaction manager instance on the connection.
+ * Get the fetch argument to be applied when selecting.
*
- * @param \Illuminate\Database\DatabaseTransactionsManager $manager
- * @return $this
+ * @return mixed
*/
- public function setTransactionManager($manager)
+ public function getFetchArgument()
{
- $this->transactionsManager = $manager;
-
- return $this;
+ return $this->fetchArgument;
}
/**
- * Unset the transaction manager for this connection.
+ * Get custom constructor arguments for the PDO::FETCH_CLASS fetch mode.
*
- * @return void
+ * @return array
*/
- public function unsetTransactionManager()
+ public function getFetchConstructorArgument()
{
- $this->transactionsManager = null;
+ return $this->fetchConstructorArgument;
}
/**
- * Determine if the connection is in a "dry run".
+ * Set the default fetch mode for the connection, and optional arguments for the given fetch mode.
*
- * @return bool
+ * @param int $fetchMode
+ * @param mixed $fetchArgument
+ * @param array $fetchConstructorArgument
+ * @return int
*/
- public function pretending()
+ public function setFetchMode($fetchMode, $fetchArgument = null, array $fetchConstructorArgument = [])
{
- return $this->pretending === true;
+ $this->fetchMode = $fetchMode;
+ $this->fetchArgument = $fetchArgument;
+ $this->fetchConstructorArgument = $fetchConstructorArgument;
}
/**
* Get the connection query log.
*
- * @return array{query: string, bindings: array, time: float|null}[]
+ * @return array
*/
public function getQueryLog()
{
return $this->queryLog;
}
- /**
- * Get the connection query log with embedded bindings.
- *
- * @return array
- */
- public function getRawQueryLog()
- {
- return array_map(fn (array $log) => [
- 'raw_query' => $this->queryGrammar->substituteBindingsIntoRawSql(
- $log['query'],
- $this->prepareBindings($log['bindings'])
- ),
- 'time' => $log['time'],
- ], $this->getQueryLog());
- }
-
/**
* Clear the query log.
*
@@ -1661,36 +1187,11 @@ public function getDatabaseName()
* Set the name of the connected database.
*
* @param string $database
- * @return $this
+ * @return string
*/
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;
- }
-
- /**
- * Retrieve the latest read / write type used.
- *
- * @return 'read'|'write'|null
- */
- protected function latestReadWriteTypeUsed()
- {
- return $this->readWriteType ?? $this->latestPdoTypeRetrieved;
}
/**
@@ -1707,79 +1208,25 @@ public function getTablePrefix()
* Set the table prefix in use by the connection.
*
* @param string $prefix
- * @return $this
+ * @return void
*/
public function setTablePrefix($prefix)
{
$this->tablePrefix = $prefix;
- return $this;
- }
-
- /**
- * Execute the given callback without table prefix.
- *
- * @param \Closure $callback
- * @return mixed
- */
- public function withoutTablePrefix(Closure $callback): mixed
- {
- $tablePrefix = $this->getTablePrefix();
-
- $this->setTablePrefix('');
-
- try {
- return $callback($this);
- } finally {
- $this->setTablePrefix($tablePrefix);
- }
- }
-
- /**
- * Get the server version for the connection.
- *
- * @return string
- */
- public function getServerVersion(): string
- {
- return $this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
- }
-
- /**
- * Register a connection resolver.
- *
- * @param string $driver
- * @param \Closure $callback
- * @return void
- */
- public static function resolverFor($driver, Closure $callback)
- {
- static::$resolvers[$driver] = $callback;
- }
-
- /**
- * Get the connection resolver for the given driver.
- *
- * @param string $driver
- * @return \Closure|null
- */
- public static function getResolver($driver)
- {
- return static::$resolvers[$driver] ?? null;
+ $this->getQueryGrammar()->setTablePrefix($prefix);
}
/**
- * Prepare the instance for cloning.
+ * Set the table prefix and return the grammar.
*
- * @return void
+ * @param \Illuminate\Database\Grammar $grammar
+ * @return \Illuminate\Database\Grammar
*/
- public function __clone()
+ public function withTablePrefix(Grammar $grammar)
{
- // When cloning, re-initialize grammars to reference cloned connection...
- $this->useDefaultQueryGrammar();
+ $grammar->setTablePrefix($this->tablePrefix);
- if (! is_null($this->schemaGrammar)) {
- $this->useDefaultSchemaGrammar();
- }
+ return $grammar;
}
}
diff --git a/ConnectionInterface.php b/ConnectionInterface.php
index 69e1fafa38..16eb667502 100755
--- a/ConnectionInterface.php
+++ b/ConnectionInterface.php
@@ -9,17 +9,16 @@ interface ConnectionInterface
/**
* Begin a fluent query against a database table.
*
- * @param \Closure|\Illuminate\Database\Query\Builder|\UnitEnum|string $table
- * @param string|null $as
+ * @param string $table
* @return \Illuminate\Database\Query\Builder
*/
- public function table($table, $as = null);
+ public function table($table);
/**
* Get a new raw query expression.
*
* @param mixed $value
- * @return \Illuminate\Contracts\Database\Query\Expression
+ * @return \Illuminate\Database\Query\Expression
*/
public function raw($value);
@@ -27,51 +26,25 @@ public function raw($value);
* Run a select statement and return a single result.
*
* @param string $query
- * @param array $bindings
- * @param bool $useReadPdo
+ * @param array $bindings
* @return mixed
*/
- public function selectOne($query, $bindings = [], $useReadPdo = true);
-
- /**
- * 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);
+ public function selectOne($query, $bindings = []);
/**
* Run a select statement against the database.
*
* @param string $query
- * @param array $bindings
- * @param bool $useReadPdo
- * @param array $fetchUsing
+ * @param array $bindings
* @return array
*/
- public function select($query, $bindings = [], $useReadPdo = true, array $fetchUsing = []);
-
- /**
- * Run a select statement against the database and returns a generator.
- *
- * @param string $query
- * @param array $bindings
- * @param bool $useReadPdo
- * @param array $fetchUsing
- * @return \Generator
- */
- public function cursor($query, $bindings = [], $useReadPdo = true, array $fetchUsing = []);
+ public function select($query, $bindings = []);
/**
* Run an insert statement against the database.
*
* @param string $query
- * @param array $bindings
+ * @param array $bindings
* @return bool
*/
public function insert($query, $bindings = []);
@@ -80,7 +53,7 @@ public function insert($query, $bindings = []);
* Run an update statement against the database.
*
* @param string $query
- * @param array $bindings
+ * @param array $bindings
* @return int
*/
public function update($query, $bindings = []);
@@ -89,7 +62,7 @@ public function update($query, $bindings = []);
* Run a delete statement against the database.
*
* @param string $query
- * @param array $bindings
+ * @param array $bindings
* @return int
*/
public function delete($query, $bindings = []);
@@ -98,7 +71,7 @@ public function delete($query, $bindings = []);
* 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 = []);
@@ -107,7 +80,7 @@ public function statement($query, $bindings = []);
* 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 = []);
@@ -132,12 +105,11 @@ public function prepareBindings(array $bindings);
* Execute a Closure within a transaction.
*
* @param \Closure $callback
- * @param int $attempts
* @return mixed
*
* @throws \Throwable
*/
- public function transaction(Closure $callback, $attempts = 1);
+ public function transaction(Closure $callback);
/**
* Start a new database transaction.
@@ -174,11 +146,4 @@ public function transactionLevel();
* @return array
*/
public function pretend(Closure $callback);
-
- /**
- * Get the name of the connected database.
- *
- * @return string
- */
- public function getDatabaseName();
}
diff --git a/ConnectionResolver.php b/ConnectionResolver.php
index b7b6279e1f..425ab6bce6 100755
--- a/ConnectionResolver.php
+++ b/ConnectionResolver.php
@@ -7,7 +7,7 @@ class ConnectionResolver implements ConnectionResolverInterface
/**
* All of the registered connections.
*
- * @var \Illuminate\Database\ConnectionInterface[]
+ * @var array
*/
protected $connections = [];
@@ -21,7 +21,8 @@ class ConnectionResolver implements ConnectionResolverInterface
/**
* Create a new connection resolver instance.
*
- * @param array $connections
+ * @param array $connections
+ * @return void
*/
public function __construct(array $connections = [])
{
@@ -33,7 +34,7 @@ public function __construct(array $connections = [])
/**
* Get a database connection instance.
*
- * @param string|null $name
+ * @param string $name
* @return \Illuminate\Database\ConnectionInterface
*/
public function connection($name = null)
diff --git a/ConnectionResolverInterface.php b/ConnectionResolverInterface.php
index 47161d37d6..eb0397a5d7 100755
--- a/ConnectionResolverInterface.php
+++ b/ConnectionResolverInterface.php
@@ -7,7 +7,7 @@ interface ConnectionResolverInterface
/**
* Get a database connection instance.
*
- * @param \UnitEnum|string|null $name
+ * @param string $name
* @return \Illuminate\Database\ConnectionInterface
*/
public function connection($name = null);
diff --git a/Connectors/ConnectionFactory.php b/Connectors/ConnectionFactory.php
index 7017c0aa5c..184789970e 100755
--- a/Connectors/ConnectionFactory.php
+++ b/Connectors/ConnectionFactory.php
@@ -2,16 +2,14 @@
namespace Illuminate\Database\Connectors;
-use Illuminate\Contracts\Container\Container;
-use Illuminate\Database\Connection;
-use Illuminate\Database\MariaDbConnection;
+use PDO;
+use Illuminate\Support\Arr;
+use InvalidArgumentException;
use Illuminate\Database\MySqlConnection;
-use Illuminate\Database\PostgresConnection;
use Illuminate\Database\SQLiteConnection;
+use Illuminate\Database\PostgresConnection;
use Illuminate\Database\SqlServerConnection;
-use Illuminate\Support\Arr;
-use InvalidArgumentException;
-use PDOException;
+use Illuminate\Contracts\Container\Container;
class ConnectionFactory
{
@@ -26,6 +24,7 @@ class ConnectionFactory
* Create a new connection factory instance.
*
* @param \Illuminate\Contracts\Container\Container $container
+ * @return void
*/
public function __construct(Container $container)
{
@@ -35,8 +34,8 @@ public function __construct(Container $container)
/**
* Establish a PDO connection based on the configuration.
*
- * @param array $config
- * @param string|null $name
+ * @param array $config
+ * @param string $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
@@ -50,18 +49,6 @@ public function make(array $config, $name = null)
return $this->createSingleConnection($config);
}
- /**
- * Parse and prepare the database configuration.
- *
- * @param array $config
- * @param string $name
- * @return array
- */
- protected function parseConfig(array $config, $name)
- {
- return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
- }
-
/**
* Create a single database connection instance.
*
@@ -70,15 +57,15 @@ protected function parseConfig(array $config, $name)
*/
protected function createSingleConnection(array $config)
{
- $pdo = $this->createPdoResolver($config);
+ $pdo = function () use ($config) {
+ return $this->createConnector($config)->connect($config);
+ };
- return $this->createConnection(
- $config['driver'], $pdo, $config['database'], $config['prefix'], $config
- );
+ return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config);
}
/**
- * Create a read / write database connection instance.
+ * Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
@@ -87,20 +74,20 @@ protected function createReadWriteConnection(array $config)
{
$connection = $this->createSingleConnection($this->getWriteConfig($config));
- return $connection
- ->setReadPdo($this->createReadPdo($config))
- ->setReadPdoConfig($this->getReadConfig($config));
+ return $connection->setReadPdo($this->createReadPdo($config));
}
/**
* Create a new PDO instance for reading.
*
* @param array $config
- * @return \Closure
+ * @return \PDO
*/
protected function createReadPdo(array $config)
{
- return $this->createPdoResolver($this->getReadConfig($config));
+ $readConfig = $this->getReadConfig($config);
+
+ return $this->createConnector($readConfig)->connect($readConfig);
}
/**
@@ -111,36 +98,44 @@ protected function createReadPdo(array $config)
*/
protected function getReadConfig(array $config)
{
- return $this->mergeReadWriteConfig(
- $config, $this->getReadWriteConfig($config, 'read')
- );
+ $readConfig = $this->getReadWriteConfig($config, 'read');
+
+ if (isset($readConfig['host']) && is_array($readConfig['host'])) {
+ $readConfig['host'] = count($readConfig['host']) > 1
+ ? $readConfig['host'][array_rand($readConfig['host'])]
+ : $readConfig['host'][0];
+ }
+
+ return $this->mergeReadWriteConfig($config, $readConfig);
}
/**
- * Get the write configuration for a read / write connection.
+ * Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getWriteConfig(array $config)
{
- return $this->mergeReadWriteConfig(
- $config, $this->getReadWriteConfig($config, 'write')
- );
+ $writeConfig = $this->getReadWriteConfig($config, 'write');
+
+ return $this->mergeReadWriteConfig($config, $writeConfig);
}
/**
* Get a read / write level configuration.
*
- * @param array $config
+ * @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{
- return isset($config[$type][0])
- ? Arr::random($config[$type])
- : $config[$type];
+ if (isset($config[$type][0])) {
+ return $config[$type][array_rand($config[$type])];
+ }
+
+ return $config[$type];
}
/**
@@ -156,73 +151,15 @@ protected function mergeReadWriteConfig(array $config, array $merge)
}
/**
- * Create a new Closure that resolves to a PDO instance.
- *
- * @param array $config
- * @return \Closure
- */
- protected function createPdoResolver(array $config)
- {
- return array_key_exists('host', $config)
- ? $this->createPdoResolverWithHosts($config)
- : $this->createPdoResolverWithoutHosts($config);
- }
-
- /**
- * Create a new Closure that resolves to a PDO instance with a specific host or an array of hosts.
- *
- * @param array $config
- * @return \Closure
- *
- * @throws \PDOException
- */
- protected function createPdoResolverWithHosts(array $config)
- {
- return function () use ($config) {
- foreach (Arr::shuffle($this->parseHosts($config)) as $host) {
- $config['host'] = $host;
-
- try {
- return $this->createConnector($config)->connect($config);
- } catch (PDOException $e) {
- continue;
- }
- }
-
- if (isset($e)) {
- throw $e;
- }
- };
- }
-
- /**
- * Parse the hosts configuration item into an array.
+ * Parse and prepare the database configuration.
*
- * @param array $config
+ * @param array $config
+ * @param string $name
* @return array
- *
- * @throws \InvalidArgumentException
*/
- protected function parseHosts(array $config)
- {
- $hosts = Arr::wrap($config['host']);
-
- if (empty($hosts)) {
- throw new InvalidArgumentException('Database hosts array is empty.');
- }
-
- return $hosts;
- }
-
- /**
- * Create a new Closure that resolves to a PDO instance where there is no configured host.
- *
- * @param array $config
- * @return \Closure
- */
- protected function createPdoResolverWithoutHosts(array $config)
+ protected function parseConfig(array $config, $name)
{
- return fn () => $this->createConnector($config)->connect($config);
+ return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
}
/**
@@ -243,41 +180,55 @@ public function createConnector(array $config)
return $this->container->make($key);
}
- return match ($config['driver']) {
- 'mysql' => new MySqlConnector,
- 'mariadb' => new MariaDbConnector,
- 'pgsql' => new PostgresConnector,
- 'sqlite' => new SQLiteConnector,
- 'sqlsrv' => new SqlServerConnector,
- default => throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."),
- };
+ 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']}]");
}
/**
* 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
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
- if ($resolver = Connection::getResolver($driver)) {
- return $resolver($connection, $database, $prefix, $config);
+ if ($this->container->bound($key = "db.connection.{$driver}")) {
+ return $this->container->make($key, [$connection, $database, $prefix, $config]);
}
- return match ($driver) {
- 'mysql' => new MySqlConnection($connection, $database, $prefix, $config),
- 'mariadb' => new MariaDbConnection($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}]."),
- };
+ 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]");
}
}
diff --git a/Connectors/Connector.php b/Connectors/Connector.php
index 8d0af49a9a..ea2637c7e5 100755
--- a/Connectors/Connector.php
+++ b/Connectors/Connector.php
@@ -2,10 +2,10 @@
namespace Illuminate\Database\Connectors;
+use PDO;
use Exception;
+use Illuminate\Support\Arr;
use Illuminate\Database\DetectsLostConnections;
-use PDO;
-use Throwable;
class Connector
{
@@ -25,80 +25,41 @@ class Connector
];
/**
- * Create a new PDO connection.
+ * Get the PDO options based on the configuration.
*
- * @param string $dsn
* @param array $config
- * @param array $options
- * @return \PDO
- *
- * @throws \Exception
+ * @return array
*/
- public function createConnection($dsn, array $config, array $options)
+ public function getOptions(array $config)
{
- [$username, $password] = [
- $config['username'] ?? null, $config['password'] ?? null,
- ];
+ $options = Arr::get($config, 'options', []);
- try {
- return $this->createPdoConnection(
- $dsn, $username, $password, $options
- );
- } catch (Exception $e) {
- return $this->tryAgainIfCausedByLostConnection(
- $e, $dsn, $username, $password, $options
- );
- }
- }
-
- /**
- * Create a new PDO connection instance.
- *
- * @param string $dsn
- * @param string $username
- * @param string $password
- * @param array $options
- * @return \PDO
- */
- protected function createPdoConnection($dsn, $username, #[\SensitiveParameter] $password, $options)
- {
- return version_compare(PHP_VERSION, '8.4.0', '<')
- ? new PDO($dsn, $username, $password, $options)
- : PDO::connect($dsn, $username, $password, $options); /** @phpstan-ignore staticMethod.notFound (PHP 8.4) */
+ return array_diff_key($this->options, $options) + $options;
}
/**
- * Handle an exception that occurred during connect execution.
+ * Create a new PDO connection.
*
- * @param \Throwable $e
* @param string $dsn
- * @param string $username
- * @param string $password
- * @param array $options
+ * @param array $config
+ * @param array $options
* @return \PDO
- *
- * @throws \Throwable
*/
- protected function tryAgainIfCausedByLostConnection(Throwable $e, $dsn, $username, #[\SensitiveParameter] $password, $options)
+ public function createConnection($dsn, array $config, array $options)
{
- if ($this->causedByLostConnection($e)) {
- return $this->createPdoConnection($dsn, $username, $password, $options);
- }
+ $username = Arr::get($config, 'username');
- throw $e;
- }
+ $password = Arr::get($config, 'password');
- /**
- * Get the PDO options based on the configuration.
- *
- * @param array $config
- * @return array
- */
- public function getOptions(array $config)
- {
- $options = $config['options'] ?? [];
+ try {
+ $pdo = new PDO($dsn, $username, $password, $options);
+ } catch (Exception $e) {
+ $pdo = $this->tryAgainIfCausedByLostConnection(
+ $e, $dsn, $username, $password, $options
+ );
+ }
- return array_diff_key($this->options, $options) + $options;
+ return $pdo;
}
/**
@@ -121,4 +82,25 @@ public function setDefaultOptions(array $options)
{
$this->options = $options;
}
+
+ /**
+ * Handle a exception that occurred during connect execution.
+ *
+ * @param \Exception $e
+ * @param string $dsn
+ * @param string $username
+ * @param string $password
+ * @param array $options
+ * @return \PDO
+ *
+ * @throws \Exception
+ */
+ protected function tryAgainIfCausedByLostConnection(Exception $e, $dsn, $username, $password, $options)
+ {
+ if ($this->causedByLostConnection($e)) {
+ return new PDO($dsn, $username, $password, $options);
+ }
+
+ throw $e;
+ }
}
diff --git a/Connectors/MariaDbConnector.php b/Connectors/MariaDbConnector.php
deleted file mode 100755
index b7203f87ae..0000000000
--- a/Connectors/MariaDbConnector.php
+++ /dev/null
@@ -1,32 +0,0 @@
-createConnection($dsn, $config, $options);
- if (! empty($config['database']) &&
- (! isset($config['use_db_after_connecting']) ||
- $config['use_db_after_connecting'])) {
+ if (isset($config['unix_socket'])) {
$connection->exec("use `{$config['database']}`;");
}
- $this->configureConnection($connection, $config);
+ $collation = $config['collation'];
+
+ // Next we will set the "names" and "collation" on the clients connections so
+ // a correct character set will be used by this client. The collation also
+ // is set on the server but needs to be set here on this client objects.
+ $charset = $config['charset'];
+
+ $names = "set names '$charset'".
+ (! is_null($collation) ? " collate '$collation'" : '');
+
+ $connection->prepare($names)->execute();
+
+ // Next, we will check to see if a timezone has been specified in this config
+ // and if it has we will issue a statement to modify the timezone with the
+ // database. Setting this DB timezone is an optional configuration item.
+ if (isset($config['timezone'])) {
+ $connection->prepare(
+ 'set time_zone="'.$config['timezone'].'"'
+ )->execute();
+ }
+
+ $this->setModes($connection, $config);
return $connection;
}
@@ -39,14 +58,12 @@ public function connect(array $config)
*
* 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)
{
- return $this->hasSocket($config)
- ? $this->getSocketDsn($config)
- : $this->getHostDsn($config);
+ return $this->configHasSocket($config) ? $this->getSocketDsn($config) : $this->getHostDsn($config);
}
/**
@@ -55,7 +72,7 @@ protected function getDsn(array $config)
* @param array $config
* @return bool
*/
- protected function hasSocket(array $config)
+ protected function configHasSocket(array $config)
{
return isset($config['unix_socket']) && ! empty($config['unix_socket']);
}
@@ -79,76 +96,32 @@ protected function getSocketDsn(array $config)
*/
protected function getHostDsn(array $config)
{
- return isset($config['port'])
- ? "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}"
- : "mysql:host={$config['host']};dbname={$config['database']}";
+ extract($config, EXTR_SKIP);
+
+ return isset($port)
+ ? "mysql:host={$host};port={$port};dbname={$database}"
+ : "mysql:host={$host};dbname={$database}";
}
/**
- * Configure the given PDO connection.
+ * Set the modes for the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
- protected function configureConnection(PDO $connection, array $config)
+ protected function setModes(PDO $connection, array $config)
{
- if (isset($config['isolation_level'])) {
- $connection->exec(sprintf('SET SESSION TRANSACTION ISOLATION LEVEL %s;', $config['isolation_level']));
- }
-
- $statements = [];
+ if (isset($config['modes'])) {
+ $modes = implode(',', $config['modes']);
- if (isset($config['charset'])) {
- if (isset($config['collation'])) {
- $statements[] = sprintf("NAMES '%s' COLLATE '%s'", $config['charset'], $config['collation']);
+ $connection->prepare("set session sql_mode='".$modes."'")->execute();
+ } elseif (isset($config['strict'])) {
+ if ($config['strict']) {
+ $connection->prepare("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'")->execute();
} else {
- $statements[] = sprintf("NAMES '%s'", $config['charset']);
+ $connection->prepare("set session sql_mode='NO_ENGINE_SUBSTITUTION'")->execute();
}
}
-
- if (isset($config['timezone'])) {
- $statements[] = sprintf("time_zone='%s'", $config['timezone']);
- }
-
- $sqlMode = $this->getSqlMode($connection, $config);
-
- if ($sqlMode !== null) {
- $statements[] = sprintf("SESSION sql_mode='%s'", $sqlMode);
- }
-
- if ($statements !== []) {
- $connection->exec(sprintf('SET %s;', implode(', ', $statements)));
- }
- }
-
- /**
- * Get the sql_mode value.
- *
- * @param \PDO $connection
- * @param array $config
- * @return string|null
- */
- protected function getSqlMode(PDO $connection, array $config)
- {
- if (isset($config['modes'])) {
- return implode(',', $config['modes']);
- }
-
- if (! isset($config['strict'])) {
- return null;
- }
-
- if (! $config['strict']) {
- return 'NO_ENGINE_SUBSTITUTION';
- }
-
- $version = $config['version'] ?? $connection->getAttribute(PDO::ATTR_SERVER_VERSION);
-
- if (version_compare($version, '8.0.11', '>=')) {
- return 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
- }
-
- return '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';
}
}
diff --git a/Connectors/PostgresConnector.php b/Connectors/PostgresConnector.php
index 31d2ff4732..9371c8df4f 100755
--- a/Connectors/PostgresConnector.php
+++ b/Connectors/PostgresConnector.php
@@ -2,13 +2,10 @@
namespace Illuminate\Database\Connectors;
-use Illuminate\Database\Concerns\ParsesSearchPath;
use PDO;
class PostgresConnector extends Connector implements ConnectorInterface
{
- use ParsesSearchPath;
-
/**
* The default PDO connection options.
*
@@ -32,20 +29,42 @@ public function connect(array $config)
// First we'll create the basic DSN and connection instance connecting to the
// using the configuration option specified by the developer. We will also
// set the default character set on the connections to UTF-8 by default.
- $connection = $this->createConnection(
- $this->getDsn($config), $config, $this->getOptions($config)
- );
+ $dsn = $this->getDsn($config);
+
+ $options = $this->getOptions($config);
+
+ $connection = $this->createConnection($dsn, $config, $options);
- $this->configureIsolationLevel($connection, $config);
+ $charset = $config['charset'];
+
+ $connection->prepare("set names '$charset'")->execute();
// Next, we will check to see if a timezone has been specified in this config
// and if it has we will issue a statement to modify the timezone with the
// database. Setting this DB timezone is an optional configuration item.
- $this->configureTimezone($connection, $config);
+ if (isset($config['timezone'])) {
+ $timezone = $config['timezone'];
+
+ $connection->prepare("set time zone '$timezone'")->execute();
+ }
- $this->configureSearchPath($connection, $config);
+ // Unlike MySQL, Postgres allows the concept of "schema" and a default schema
+ // may have been specified on the connections. If that is the case we will
+ // set the default schema search paths to the specified database schema.
+ if (isset($config['schema'])) {
+ $schema = $this->formatSchema($config['schema']);
- $this->configureSynchronousCommit($connection, $config);
+ $connection->prepare("set search_path to {$schema}")->execute();
+ }
+
+ // 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.
+ if (isset($config['application_name'])) {
+ $applicationName = $config['application_name'];
+
+ $connection->prepare("set application_name to '$applicationName'")->execute();
+ }
return $connection;
}
@@ -53,7 +72,7 @@ public function connect(array $config)
/**
* Create a DSN string from a configuration.
*
- * @param array $config
+ * @param array $config
* @return string
*/
protected function getDsn(array $config)
@@ -65,123 +84,34 @@ protected function getDsn(array $config)
$host = isset($host) ? "host={$host};" : '';
- // 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 ?? null;
- $port = $connect_via_port ?? $port ?? null;
-
- $dsn = "pgsql:{$host}dbname='{$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
// string back out for usage, as this has been fully constructed here.
- if (! is_null($port)) {
+ if (isset($config['port'])) {
$dsn .= ";port={$port}";
}
- if (isset($charset)) {
- $dsn .= ";client_encoding='{$charset}'";
- }
-
- // 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.
- if (isset($application_name)) {
- $dsn .= ";application_name='".str_replace("'", "\'", $application_name)."'";
- }
-
- return $this->addSslOptions($dsn, $config);
- }
-
- /**
- * Add the SSL options to the DSN.
- *
- * @param string $dsn
- * @param array $config
- * @return string
- */
- protected function addSslOptions($dsn, array $config)
- {
- foreach (['sslmode', 'sslcert', 'sslkey', 'sslrootcert'] as $option) {
- if (isset($config[$option])) {
- $dsn .= ";{$option}={$config[$option]}";
- }
+ if (isset($config['sslmode'])) {
+ $dsn .= ";sslmode={$sslmode}";
}
return $dsn;
}
/**
- * Set the connection transaction isolation level.
+ * Format the schema for the DSN.
*
- * @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 timezone on the connection.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureTimezone($connection, array $config)
- {
- if (isset($config['timezone'])) {
- $timezone = $config['timezone'];
-
- $connection->prepare("set time zone '{$timezone}'")->execute();
- }
- }
-
- /**
- * Set the "search_path" on the database connection.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureSearchPath($connection, $config)
- {
- if (isset($config['search_path']) || isset($config['schema'])) {
- $searchPath = $this->quoteSearchPath(
- $this->parseSearchPath($config['search_path'] ?? $config['schema'])
- );
-
- $connection->prepare("set search_path to {$searchPath}")->execute();
- }
- }
-
- /**
- * Format the search path for the DSN.
- *
- * @param array $searchPath
+ * @param array|string $schema
* @return string
*/
- protected function quoteSearchPath($searchPath)
- {
- return count($searchPath) === 1 ? '"'.$searchPath[0].'"' : '"'.implode('", "', $searchPath).'"';
- }
-
- /**
- * Configure the synchronous_commit setting.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureSynchronousCommit($connection, array $config)
+ protected function formatSchema($schema)
{
- if (isset($config['synchronous_commit'])) {
- $connection->prepare("set synchronous_commit to '{$config['synchronous_commit']}'")->execute();
+ if (is_array($schema)) {
+ return '"'.implode('", "', $schema).'"';
+ } else {
+ return '"'.$schema.'"';
}
}
}
diff --git a/Connectors/SQLiteConnector.php b/Connectors/SQLiteConnector.php
index 858549ec55..28f90915b5 100755
--- a/Connectors/SQLiteConnector.php
+++ b/Connectors/SQLiteConnector.php
@@ -2,7 +2,7 @@
namespace Illuminate\Database\Connectors;
-use Illuminate\Database\SQLiteDatabaseDoesNotExistException;
+use InvalidArgumentException;
class SQLiteConnector extends Connector implements ConnectorInterface
{
@@ -11,139 +11,29 @@ class SQLiteConnector extends Connector implements ConnectorInterface
*
* @param array $config
* @return \PDO
+ *
+ * @throws \InvalidArgumentException
*/
public function connect(array $config)
{
$options = $this->getOptions($config);
- $path = $this->parseDatabasePath($config['database']);
-
- $connection = $this->createConnection("sqlite:{$path}", $config, $options);
-
- $this->configurePragmas($connection, $config);
- $this->configureForeignKeyConstraints($connection, $config);
- $this->configureBusyTimeout($connection, $config);
- $this->configureJournalMode($connection, $config);
- $this->configureSynchronous($connection, $config);
-
- return $connection;
- }
-
- /**
- * Get the absolute database path.
- *
- * @param string $path
- * @return string
- *
- * @throws \Illuminate\Database\SQLiteDatabaseDoesNotExistException
- */
- protected function parseDatabasePath(string $path): string
- {
- $database = $path;
-
// 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 shall be anonymous (:memory:) or named.
- if ($path === ':memory:' ||
- str_contains($path, '?mode=memory') ||
- str_contains($path, '&mode=memory')
- ) {
- return $path;
+ // querying. In-memory databases may only have a single open connection.
+ if ($config['database'] == ':memory:') {
+ return $this->createConnection('sqlite::memory:', $config, $options);
}
- $path = realpath($path) ?: realpath(base_path($path));
+ $path = realpath($config['database']);
// Here we'll verify that the SQLite database exists before going any further
// 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 SQLiteDatabaseDoesNotExistException($database);
- }
-
- return $path;
- }
-
- /**
- * Set miscellaneous user-configured pragmas.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configurePragmas($connection, array $config): void
- {
- if (! isset($config['pragmas'])) {
- return;
- }
-
- foreach ($config['pragmas'] as $pragma => $value) {
- $connection->prepare("pragma {$pragma} = {$value}")->execute();
- }
- }
-
- /**
- * Enable or disable foreign key constraints if configured.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureForeignKeyConstraints($connection, array $config): void
- {
- if (! isset($config['foreign_key_constraints'])) {
- return;
- }
-
- $foreignKeys = $config['foreign_key_constraints'] ? 1 : 0;
-
- $connection->prepare("pragma foreign_keys = {$foreignKeys}")->execute();
- }
-
- /**
- * Set the busy timeout if configured.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureBusyTimeout($connection, array $config): void
- {
- if (! isset($config['busy_timeout'])) {
- return;
- }
-
- $connection->prepare("pragma busy_timeout = {$config['busy_timeout']}")->execute();
- }
-
- /**
- * Set the journal mode if configured.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureJournalMode($connection, array $config): void
- {
- if (! isset($config['journal_mode'])) {
- return;
- }
-
- $connection->prepare("pragma journal_mode = {$config['journal_mode']}")->execute();
- }
-
- /**
- * Set the synchronous mode if configured.
- *
- * @param \PDO $connection
- * @param array $config
- * @return void
- */
- protected function configureSynchronous($connection, array $config): void
- {
- if (! isset($config['synchronous'])) {
- return;
+ throw new InvalidArgumentException("Database (${config['database']}) does not exist.");
}
- $connection->prepare("pragma synchronous = {$config['synchronous']}")->execute();
+ return $this->createConnection("sqlite:{$path}", $config, $options);
}
}
diff --git a/Connectors/SqlServerConnector.php b/Connectors/SqlServerConnector.php
index 14cb72dbbf..7a2a6c8146 100755
--- a/Connectors/SqlServerConnector.php
+++ b/Connectors/SqlServerConnector.php
@@ -2,8 +2,8 @@
namespace Illuminate\Database\Connectors;
-use Illuminate\Support\Arr;
use PDO;
+use Illuminate\Support\Arr;
class SqlServerConnector extends Connector implements ConnectorInterface
{
@@ -29,37 +29,13 @@ public function connect(array $config)
{
$options = $this->getOptions($config);
- $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();
+ return $this->createConnection($this->getDsn($config), $config, $options);
}
/**
* Create a DSN string from a configuration.
*
- * @param array $config
+ * @param array $config
* @return string
*/
protected function getDsn(array $config)
@@ -67,41 +43,45 @@ protected function getDsn(array $config)
// 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 ($this->prefersOdbc($config)) {
+ if (in_array('dblib', $this->getAvailableDrivers())) {
+ return $this->getDblibDsn($config);
+ } elseif ($this->prefersOdbc($config)) {
return $this->getOdbcDsn($config);
- }
-
- if (in_array('sqlsrv', $this->getAvailableDrivers())) {
- return $this->getSqlSrvDsn($config);
} else {
- return $this->getDblibDsn($config);
+ return $this->getSqlSrvDsn($config);
}
}
/**
- * Determine if the database configuration prefers ODBC.
+ * Get the DSN string for a DbLib connection.
*
* @param array $config
- * @return bool
+ * @return string
*/
- protected function prefersOdbc(array $config)
+ protected function getDblibDsn(array $config)
{
- return in_array('odbc', $this->getAvailableDrivers()) &&
- ($config['odbc'] ?? null) === true;
+ $arguments = [
+ 'host' => $this->buildHostString($config, ':'),
+ 'dbname' => $config['database'],
+ ];
+
+ $arguments = array_merge(
+ $arguments, Arr::only($config, ['appname', 'charset'])
+ );
+
+ return $this->buildConnectString('dblib', $arguments);
}
/**
- * Get the DSN string for a DbLib connection.
+ * Determine if the database configuration prefers ODBC.
*
* @param array $config
- * @return string
+ * @return bool
*/
- protected function getDblibDsn(array $config)
+ protected function prefersOdbc(array $config)
{
- return $this->buildConnectString('dblib', array_merge([
- 'host' => $this->buildHostString($config, ':'),
- 'dbname' => $config['database'],
- ], Arr::only($config, ['appname', 'charset', 'version'])));
+ return in_array('odbc', $this->getAvailableDrivers()) &&
+ array_get($config, 'odbc') === true;
}
/**
@@ -112,9 +92,11 @@ protected function getDblibDsn(array $config)
*/
protected function getOdbcDsn(array $config)
{
- return isset($config['odbc_datasource_name'])
- ? 'odbc:'.$config['odbc_datasource_name']
- : '';
+ if (isset($config['odbc_datasource_name'])) {
+ return 'odbc:'.$config['odbc_datasource_name'];
+ }
+
+ return '';
}
/**
@@ -133,60 +115,16 @@ protected function getSqlSrvDsn(array $config)
$arguments['Database'] = $config['database'];
}
- if (isset($config['readonly'])) {
- $arguments['ApplicationIntent'] = 'ReadOnly';
- }
-
- if (isset($config['pooling']) && $config['pooling'] === false) {
- $arguments['ConnectionPooling'] = '0';
- }
-
if (isset($config['appname'])) {
$arguments['APP'] = $config['appname'];
}
- if (isset($config['encrypt'])) {
- $arguments['Encrypt'] = $config['encrypt'];
- }
-
- if (isset($config['trust_server_certificate'])) {
- $arguments['TrustServerCertificate'] = $config['trust_server_certificate'];
- }
-
- if (isset($config['multiple_active_result_sets']) && $config['multiple_active_result_sets'] === false) {
- $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['readonly'])) {
+ $arguments['ApplicationIntent'] = 'ReadOnly';
}
- if (isset($config['authentication'])) {
- $arguments['Authentication'] = $config['authentication'];
+ if (isset($config['pooling']) && $config['pooling'] === false) {
+ $arguments['ConnectionPooling'] = '0';
}
return $this->buildConnectString('sqlsrv', $arguments);
@@ -201,9 +139,11 @@ protected function getSqlSrvDsn(array $config)
*/
protected function buildConnectString($driver, array $arguments)
{
- return $driver.':'.implode(';', array_map(function ($key) use ($arguments) {
+ $options = array_map(function ($key) use ($arguments) {
return sprintf('%s=%s', $key, $arguments[$key]);
- }, array_keys($arguments)));
+ }, array_keys($arguments));
+
+ return $driver.':'.implode(';', $options);
}
/**
@@ -215,11 +155,11 @@ protected function buildConnectString($driver, array $arguments)
*/
protected function buildHostString(array $config, $separator)
{
- if (empty($config['port'])) {
+ if (isset($config['port'])) {
+ return $config['host'].$separator.$config['port'];
+ } else {
return $config['host'];
}
-
- return $config['host'].$separator.$config['port'];
}
/**
diff --git a/Console/DatabaseInspectionCommand.php b/Console/DatabaseInspectionCommand.php
deleted file mode 100644
index 8faab04147..0000000000
--- a/Console/DatabaseInspectionCommand.php
+++ /dev/null
@@ -1,50 +0,0 @@
-getDriverTitle();
- }
-
- /**
- * Get the number of open connections for a database.
- *
- * @param \Illuminate\Database\ConnectionInterface $connection
- * @return int|null
- *
- * @deprecated
- */
- protected function getConnectionCount(ConnectionInterface $connection)
- {
- return $connection->threadCount();
- }
-
- /**
- * Get the connection configuration details for the given connection.
- *
- * @param string|null $database
- * @return array
- */
- protected function getConfigFromDatabase($database)
- {
- $database ??= config('database.default');
-
- return Arr::except(config('database.connections.'.$database), ['password']);
- }
-}
diff --git a/Console/DbCommand.php b/Console/DbCommand.php
deleted file mode 100644
index 3017607355..0000000000
--- a/Console/DbCommand.php
+++ /dev/null
@@ -1,257 +0,0 @@
-getConnection();
-
- if (! isset($connection['host']) && $connection['driver'] !== 'sqlite') {
- $this->components->error('No host specified for this database connection.');
- $this->line(' Use the [--read]> and [--write]> options to specify a read or write connection.');
- $this->newLine();
-
- return Command::FAILURE;
- }
-
- try {
- (new Process(
- array_merge([$command = $this->getCommand($connection)], $this->commandArguments($connection)),
- null,
- $this->commandEnvironment($connection)
- ))->setTimeout(null)->setTty(true)->mustRun(function ($type, $buffer) {
- $this->output->write($buffer);
- });
- } catch (ProcessFailedException $e) {
- throw_unless($e->getProcess()->getExitCode() === 127, $e);
-
- $this->error("{$command} not found in path.");
-
- return Command::FAILURE;
- }
-
- 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',
- 'mariadb' => 'mariadb',
- '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)
- {
- $optionalArguments = [
- 'password' => '--password='.$connection['password'],
- 'unix_socket' => '--socket='.($connection['unix_socket'] ?? ''),
- 'charset' => '--default-character-set='.($connection['charset'] ?? ''),
- ];
-
- if (! $connection['password']) {
- unset($optionalArguments['password']);
- }
-
- return array_merge([
- '--host='.$connection['host'],
- '--port='.$connection['port'],
- '--user='.$connection['username'],
- ], $this->getOptionalArguments($optionalArguments, $connection), [$connection['database']]);
- }
-
- /**
- * Get the arguments for the MariaDB CLI.
- *
- * @param array $connection
- * @return array
- */
- protected function getMariaDbArguments(array $connection)
- {
- return $this->getMysqlArguments($connection);
- }
-
- /**
- * 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'] : ''), ],
- 'trust_server_certificate' => ['-C'],
- ], $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));
- }
-}
diff --git a/Console/DumpCommand.php b/Console/DumpCommand.php
deleted file mode 100644
index fea8fc0554..0000000000
--- a/Console/DumpCommand.php
+++ /dev/null
@@ -1,104 +0,0 @@
-isProhibited()) {
- return Command::FAILURE;
- }
-
- $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(
- $path = database_path('migrations'), preserve: false
- );
-
- $info .= ' and pruned';
-
- $dispatcher->dispatch(new MigrationsPruned($connection, $path));
- }
-
- $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)
- {
- $migrations = Config::get('database.migrations', 'migrations');
-
- $migrationTable = is_array($migrations) ? ($migrations['table'] ?? 'migrations') : $migrations;
-
- return $connection->getSchemaState()
- ->withMigrationTable($migrationTable)
- ->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.sql'), function ($path) {
- (new Filesystem)->ensureDirectoryExists(dirname($path));
- });
- }
-}
diff --git a/Console/Factories/FactoryMakeCommand.php b/Console/Factories/FactoryMakeCommand.php
deleted file mode 100644
index 6d080a1439..0000000000
--- a/Console/Factories/FactoryMakeCommand.php
+++ /dev/null
@@ -1,144 +0,0 @@
-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 = (new Stringable($name))->replaceFirst($this->rootNamespace(), '')->finish('Factory')->value();
-
- 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'],
- ];
- }
-}
diff --git a/Console/Factories/stubs/factory.stub b/Console/Factories/stubs/factory.stub
deleted file mode 100644
index f931493a27..0000000000
--- a/Console/Factories/stubs/factory.stub
+++ /dev/null
@@ -1,23 +0,0 @@
-
- */
-class {{ factory }}Factory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- //
- ];
- }
-}
diff --git a/Console/Migrations/BaseCommand.php b/Console/Migrations/BaseCommand.php
index a250d2945f..5fca1144a8 100755
--- a/Console/Migrations/BaseCommand.php
+++ b/Console/Migrations/BaseCommand.php
@@ -3,43 +3,9 @@
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
-use Illuminate\Support\Collection;
class BaseCommand extends Command
{
- /**
- * Get all of the migration paths.
- *
- * @return string[]
- */
- protected function getMigrationPaths()
- {
- // Here, 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 the installation folder so our database
- // migrations may be run for any customized path from within the application.
- if ($this->input->hasOption('path') && $this->option('path')) {
- return (new Collection($this->option('path')))->map(function ($path) {
- return ! $this->usingRealPath()
- ? $this->laravel->basePath().'/'.$path
- : $path;
- })->all();
- }
-
- return array_merge(
- $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.
*
diff --git a/Console/Migrations/FreshCommand.php b/Console/Migrations/FreshCommand.php
deleted file mode 100644
index 723d3c2298..0000000000
--- a/Console/Migrations/FreshCommand.php
+++ /dev/null
@@ -1,148 +0,0 @@
-migrator = $migrator;
- }
-
- /**
- * Execute the console command.
- *
- * @return int
- */
- public function handle()
- {
- if ($this->isProhibited() ||
- ! $this->confirmToProceed()) {
- return Command::FAILURE;
- }
-
- $database = $this->input->getOption('database');
-
- $this->migrator->usingConnection($database, function () use ($database) {
- if ($this->migrator->repositoryExists()) {
- $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($database, $this->needsSeeding())
- );
- }
-
- 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'],
- ];
- }
-}
diff --git a/Console/Migrations/InstallCommand.php b/Console/Migrations/InstallCommand.php
index b89cd4b4e8..103dcaa928 100755
--- a/Console/Migrations/InstallCommand.php
+++ b/Console/Migrations/InstallCommand.php
@@ -3,11 +3,9 @@
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
-use Illuminate\Database\Migrations\MigrationRepositoryInterface;
-use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
+use Illuminate\Database\Migrations\MigrationRepositoryInterface;
-#[AsCommand(name: 'migrate:install')]
class InstallCommand extends Command
{
/**
@@ -35,6 +33,7 @@ class InstallCommand extends Command
* Create a new migration install command instance.
*
* @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
+ * @return void
*/
public function __construct(MigrationRepositoryInterface $repository)
{
@@ -48,15 +47,13 @@ public function __construct(MigrationRepositoryInterface $repository)
*
* @return void
*/
- public function handle()
+ public function fire()
{
$this->repository->setSource($this->input->getOption('database'));
- if (! $this->repository->repositoryExists()) {
- $this->repository->createRepository();
- }
+ $this->repository->createRepository();
- $this->components->info('Migration table created successfully.');
+ $this->info('Migration table created successfully.');
}
/**
@@ -67,7 +64,7 @@ public function handle()
protected function getOptions()
{
return [
- ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
+ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
];
}
}
diff --git a/Console/Migrations/MigrateCommand.php b/Console/Migrations/MigrateCommand.php
index e3116ca3d1..38c51b66e4 100755
--- a/Console/Migrations/MigrateCommand.php
+++ b/Console/Migrations/MigrateCommand.php
@@ -3,40 +3,19 @@
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 Illuminate\Support\Str;
-use PDOException;
-use RuntimeException;
-use Symfony\Component\Console\Attribute\AsCommand;
-use Throwable;
+use Symfony\Component\Console\Input\InputOption;
-use function Laravel\Prompts\confirm;
-
-#[AsCommand(name: 'migrate')]
-class MigrateCommand extends BaseCommand implements Isolatable
+class MigrateCommand extends BaseCommand
{
use ConfirmableTrait;
/**
- * The name and signature of the console command.
+ * The console command name.
*
* @var string
*/
- 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}
- {--graceful : Return a successful exit code even if an error occurs}';
+ protected $name = 'migrate';
/**
* The console command description.
@@ -52,84 +31,64 @@ class MigrateCommand extends BaseCommand implements Isolatable
*/
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, Dispatcher $dispatcher)
+ public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
- $this->dispatcher = $dispatcher;
}
/**
* Execute the console command.
*
- * @return int
- *
- * @throws \Throwable
+ * @return void
*/
- public function handle()
+ public function fire()
{
if (! $this->confirmToProceed()) {
- return 1;
+ return;
}
- try {
- $this->runMigrations();
- } catch (Throwable $e) {
- if ($this->option('graceful')) {
- $this->components->warn($e->getMessage());
+ $this->prepareDatabase();
- return 0;
- }
+ // The pretend option can be used for "simulating" the migration and grabbing
+ // the SQL queries that would fire if the migration were to be run against
+ // a database for real, which is helpful for double checking migrations.
+ $pretend = $this->input->getOption('pretend');
- throw $e;
+ // 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.
+ if (! is_null($path = $this->input->getOption('path'))) {
+ $path = $this->laravel->basePath().'/'.$path;
+ } else {
+ $path = $this->getMigrationPath();
}
- return 0;
- }
-
- /**
- * Run the pending migrations.
- *
- * @return void
- */
- protected function runMigrations()
- {
- $this->migrator->usingConnection($this->option('database'), function () {
- $this->prepareDatabase();
+ $this->migrator->run($path, [
+ 'pretend' => $pretend,
+ 'step' => $this->input->getOption('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.
- $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->input->getOption('seed')) {
+ $this->call('db:seed', ['--force' => true]);
+ }
}
/**
@@ -139,205 +98,34 @@ protected function runMigrations()
*/
protected function prepareDatabase()
{
- if (! $this->repositoryExists()) {
- $this->components->info('Preparing 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 {
- return $this->handleMissingDatabase($e->getPrevious());
- } catch (Throwable) {
- return false;
- }
- });
- }
-
- /**
- * Attempt to create the database if it is missing.
- *
- * @param \Throwable $e
- * @return bool
- */
- protected function handleMissingDatabase(Throwable $e)
- {
- if ($e instanceof SQLiteDatabaseDoesNotExistException) {
- return $this->createMissingSqliteDatabase($e->path);
- }
-
- $connection = $this->migrator->resolveConnection($this->option('database'));
-
- if (! $e instanceof PDOException) {
- return false;
- }
-
- if (($e->getCode() === 1049 && in_array($connection->getDriverName(), ['mysql', 'mariadb'])) ||
- (($e->errorInfo[0] ?? null) == '08006' &&
- $connection->getDriverName() == 'pgsql' &&
- Str::contains($e->getMessage(), '"'.$connection->getDatabaseName().'"'))) {
- return $this->createMissingMySqlOrPgsqlDatabase($connection);
- }
-
- return false;
- }
-
- /**
- * Create a missing SQLite database.
- *
- * @param string $path
- * @return bool
- *
- * @throws \RuntimeException
- */
- protected function createMissingSqliteDatabase($path)
- {
- if ($this->option('force')) {
- return touch($path);
- }
-
- if ($this->option('no-interaction')) {
- return false;
- }
+ $this->migrator->setConnection($this->input->getOption('database'));
- $this->components->warn('The SQLite database configured for this application does not exist: '.$path);
+ if (! $this->migrator->repositoryExists()) {
+ $options = ['--database' => $this->input->getOption('database')];
- if (! confirm('Would you like to create it?', default: true)) {
- $this->components->info('Operation cancelled. No database was created.');
-
- throw new RuntimeException('Database was not created. Aborting migration.');
+ $this->call('migrate:install', $options);
}
-
- return touch($path);
}
/**
- * Create a missing MySQL or Postgres database.
+ * Get the console command options.
*
- * @param \Illuminate\Database\Connection $connection
- * @return bool
- *
- * @throws \RuntimeException
+ * @return array
*/
- protected function createMissingMySqlOrPgsqlDatabase($connection)
+ protected function getOptions()
{
- 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.");
+ return [
+ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
- if (! confirm('Would you like to create it?', default: true)) {
- $this->components->info('Operation cancelled. No database was created.');
+ ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
- throw new RuntimeException('Database was not created. Aborting migration.');
- }
- }
- try {
- $this->laravel['config']->set(
- "database.connections.{$connection->getName()}.database",
- match ($connection->getDriverName()) {
- 'mysql', 'mariadb' => null,
- 'pgsql' => 'postgres',
- },
- );
+ ['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'],
- $this->laravel['db']->purge();
+ ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'],
- $freshConnection = $this->migrator->resolveConnection($this->option('database'));
-
- return tap($freshConnection->unprepared(
- match ($connection->getDriverName()) {
- 'mysql', 'mariadb' => "CREATE DATABASE IF NOT EXISTS `{$connection->getDatabaseName()}`",
- 'pgsql' => 'CREATE DATABASE "'.$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;
- }
+ ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'],
- return database_path('schema/'.$connection->getName().'-schema.sql');
+ ['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually.'],
+ ];
}
}
diff --git a/Console/Migrations/MigrateMakeCommand.php b/Console/Migrations/MigrateMakeCommand.php
index ac5077f58d..9858fa5684 100644
--- a/Console/Migrations/MigrateMakeCommand.php
+++ b/Console/Migrations/MigrateMakeCommand.php
@@ -2,26 +2,20 @@
namespace Illuminate\Database\Console\Migrations;
-use Illuminate\Contracts\Console\PromptsForMissingInput;
-use Illuminate\Database\Migrations\MigrationCreator;
use Illuminate\Support\Composer;
-use Illuminate\Support\Str;
-use Symfony\Component\Console\Attribute\AsCommand;
+use Illuminate\Database\Migrations\MigrationCreator;
-#[AsCommand(name: 'make:migration')]
-class MigrateMakeCommand extends BaseCommand implements PromptsForMissingInput
+class MigrateMakeCommand extends BaseCommand
{
/**
* The console command signature.
*
* @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}
- {--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
- {--fullpath : Output the full path of the migration (Deprecated)}';
+ 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.}';
/**
* The console command description.
@@ -41,8 +35,6 @@ class MigrateMakeCommand extends BaseCommand implements PromptsForMissingInput
* The Composer instance.
*
* @var \Illuminate\Support\Composer
- *
- * @deprecated Will be removed in a future Laravel version.
*/
protected $composer;
@@ -51,6 +43,7 @@ class MigrateMakeCommand extends BaseCommand implements PromptsForMissingInput
*
* @param \Illuminate\Database\Migrations\MigrationCreator $creator
* @param \Illuminate\Support\Composer $composer
+ * @return void
*/
public function __construct(MigrationCreator $creator, Composer $composer)
{
@@ -65,37 +58,29 @@ public function __construct(MigrationCreator $creator, Composer $composer)
*
* @return void
*/
- public function handle()
+ public function fire()
{
// 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 = Str::snake(trim($this->input->getArgument('name')));
+ $name = trim($this->input->getArgument('name'));
$table = $this->input->getOption('table');
$create = $this->input->getOption('create') ?: false;
- // If no table was given as an option but a create option is given then we
- // will use the "create" option as the table name. This allows the devs
- // to pass a table name into this option as a short-cut for creating.
if (! $table && is_string($create)) {
$table = $create;
$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.
$this->writeMigration($name, $table, $create);
+
+ $this->composer->dumpAutoloads();
}
/**
@@ -103,20 +88,16 @@ public function handle()
*
* @param string $name
* @param string $table
- * @param bool $create
- * @return void
+ * @param bool $create
+ * @return string
*/
protected function writeMigration($name, $table, $create)
{
- $file = $this->creator->create(
- $name, $this->getMigrationPath(), $table, $create
- );
+ $path = $this->getMigrationPath();
- if (windows_os()) {
- $file = str_replace('/', '\\', $file);
- }
+ $file = pathinfo($this->creator->create($name, $path, $table, $create), PATHINFO_FILENAME);
- $this->components->info(sprintf('Migration [%s] created successfully.', $file));
+ $this->line("Created Migration: $file");
}
/**
@@ -127,23 +108,9 @@ protected function writeMigration($name, $table, $create)
protected function getMigrationPath()
{
if (! is_null($targetPath = $this->input->getOption('path'))) {
- return ! $this->usingRealPath()
- ? $this->laravel->basePath().'/'.$targetPath
- : $targetPath;
+ return $this->laravel->basePath().'/'.$targetPath;
}
return parent::getMigrationPath();
}
-
- /**
- * Prompt for missing input arguments using the returned questions.
- *
- * @return array
- */
- protected function promptForMissingArgumentsUsing()
- {
- return [
- 'name' => ['What should the migration be named?', 'E.g. create_flights_table'],
- ];
- }
}
diff --git a/Console/Migrations/RefreshCommand.php b/Console/Migrations/RefreshCommand.php
index 7d74f5b38c..ce4e8f1938 100755
--- a/Console/Migrations/RefreshCommand.php
+++ b/Console/Migrations/RefreshCommand.php
@@ -4,16 +4,11 @@
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
-use Illuminate\Console\Prohibitable;
-use Illuminate\Contracts\Events\Dispatcher;
-use Illuminate\Database\Events\DatabaseRefreshed;
-use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
-#[AsCommand(name: 'migrate:refresh')]
class RefreshCommand extends Command
{
- use ConfirmableTrait, Prohibitable;
+ use ConfirmableTrait;
/**
* The console command name.
@@ -32,90 +27,36 @@ class RefreshCommand extends Command
/**
* Execute the console command.
*
- * @return int
+ * @return void
*/
- public function handle()
+ public function fire()
{
- if ($this->isProhibited() ||
- ! $this->confirmToProceed()) {
- return Command::FAILURE;
+ if (! $this->confirmToProceed()) {
+ return;
}
- // Next we'll gather some of the options so that we can have the right options
- // to pass to the commands. This includes options such as which database to
- // use and the path to use for the migration. Then we'll run the command.
$database = $this->input->getOption('database');
+ $force = $this->input->getOption('force');
+
$path = $this->input->getOption('path');
- // 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);
- } else {
- $this->runReset($database, $path);
- }
+ $this->call('migrate:reset', [
+ '--database' => $database, '--force' => $force,
+ ]);
// 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', array_filter([
+ $this->call('migrate', [
'--database' => $database,
+ '--force' => $force,
'--path' => $path,
- '--realpath' => $this->input->getOption('realpath'),
- '--force' => true,
- ]));
-
- if ($this->laravel->bound(Dispatcher::class)) {
- $this->laravel[Dispatcher::class]->dispatch(
- new DatabaseRefreshed($database, $this->needsSeeding())
- );
- }
+ ]);
if ($this->needsSeeding()) {
$this->runSeeder($database);
}
-
- return 0;
- }
-
- /**
- * Run the rollback command.
- *
- * @param string $database
- * @param string $path
- * @param int $step
- * @return void
- */
- protected function runRollback($database, $path, $step)
- {
- $this->call('migrate:rollback', array_filter([
- '--database' => $database,
- '--path' => $path,
- '--realpath' => $this->input->getOption('realpath'),
- '--step' => $step,
- '--force' => true,
- ]));
- }
-
- /**
- * Run the reset command.
- *
- * @param string $database
- * @param string $path
- * @return void
- */
- protected function runReset($database, $path)
- {
- $this->call('migrate:reset', array_filter([
- '--database' => $database,
- '--path' => $path,
- '--realpath' => $this->input->getOption('realpath'),
- '--force' => true,
- ]));
}
/**
@@ -136,11 +77,13 @@ protected function needsSeeding()
*/
protected function runSeeder($database)
{
- $this->call('db:seed', array_filter([
- '--database' => $database,
- '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
- '--force' => true,
- ]));
+ $class = $this->option('seeder') ?: 'DatabaseSeeder';
+
+ $force = $this->input->getOption('force');
+
+ $this->call('db:seed', [
+ '--database' => $database, '--class' => $class, '--force' => $force,
+ ]);
}
/**
@@ -151,13 +94,15 @@ protected function runSeeder($database)
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 | 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'],
+ ['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.'],
];
}
}
diff --git a/Console/Migrations/ResetCommand.php b/Console/Migrations/ResetCommand.php
index 787801bab2..8871d3d02b 100755
--- a/Console/Migrations/ResetCommand.php
+++ b/Console/Migrations/ResetCommand.php
@@ -4,15 +4,12 @@
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
-use Illuminate\Console\Prohibitable;
use Illuminate\Database\Migrations\Migrator;
-use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
-#[AsCommand(name: 'migrate:reset')]
-class ResetCommand extends BaseCommand
+class ResetCommand extends Command
{
- use ConfirmableTrait, Prohibitable;
+ use ConfirmableTrait;
/**
* The console command name.
@@ -39,6 +36,7 @@ class ResetCommand extends BaseCommand
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
+ * @return void
*/
public function __construct(Migrator $migrator)
{
@@ -50,27 +48,32 @@ public function __construct(Migrator $migrator)
/**
* Execute the console command.
*
- * @return int
+ * @return void
*/
- public function handle()
+ public function fire()
{
- if ($this->isProhibited() ||
- ! $this->confirmToProceed()) {
- return Command::FAILURE;
+ if (! $this->confirmToProceed()) {
+ return;
}
- 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.');
- }
-
- $this->migrator->setOutput($this->output)->reset(
- $this->getMigrationPaths(), $this->option('pretend')
- );
- });
+ $this->migrator->setConnection($this->input->getOption('database'));
+
+ if (! $this->migrator->repositoryExists()) {
+ $this->output->writeln('Migration table not found.');
+
+ return;
+ }
+
+ $pretend = $this->input->getOption('pretend');
+
+ $this->migrator->reset($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);
+ }
}
/**
@@ -81,15 +84,11 @@ public function handle()
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 | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
+ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
- ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
+ ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
- ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'],
+ ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'],
];
}
}
diff --git a/Console/Migrations/RollbackCommand.php b/Console/Migrations/RollbackCommand.php
index 9c3543ec5b..a341b4fecb 100755
--- a/Console/Migrations/RollbackCommand.php
+++ b/Console/Migrations/RollbackCommand.php
@@ -4,15 +4,12 @@
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
-use Illuminate\Console\Prohibitable;
use Illuminate\Database\Migrations\Migrator;
-use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
-#[AsCommand('migrate:rollback')]
-class RollbackCommand extends BaseCommand
+class RollbackCommand extends Command
{
- use ConfirmableTrait, Prohibitable;
+ use ConfirmableTrait;
/**
* The console command name.
@@ -39,6 +36,7 @@ class RollbackCommand extends BaseCommand
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
+ * @return void
*/
public function __construct(Migrator $migrator)
{
@@ -50,26 +48,26 @@ public function __construct(Migrator $migrator)
/**
* Execute the console command.
*
- * @return int
+ * @return void
*/
- public function handle()
+ public function fire()
{
- if ($this->isProhibited() ||
- ! $this->confirmToProceed()) {
- return Command::FAILURE;
+ if (! $this->confirmToProceed()) {
+ return;
}
- $this->migrator->usingConnection($this->option('database'), function () {
- $this->migrator->setOutput($this->output)->rollback(
- $this->getMigrationPaths(), [
- 'pretend' => $this->option('pretend'),
- 'step' => (int) $this->option('step'),
- 'batch' => (int) $this->option('batch'),
- ]
- );
- });
-
- return 0;
+ $this->migrator->setConnection($this->input->getOption('database'));
+
+ $pretend = $this->input->getOption('pretend');
+
+ $this->migrator->rollback($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);
+ }
}
/**
@@ -80,13 +78,11 @@ public function handle()
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 | 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'],
- ['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'],
- ['batch', null, InputOption::VALUE_REQUIRED, 'The batch of migrations (identified by their batch number) to be reverted'],
+ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
+
+ ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'],
+
+ ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'],
];
}
}
diff --git a/Console/Migrations/StatusCommand.php b/Console/Migrations/StatusCommand.php
index cbb16a133c..aba7acf036 100644
--- a/Console/Migrations/StatusCommand.php
+++ b/Console/Migrations/StatusCommand.php
@@ -3,12 +3,8 @@
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Database\Migrations\Migrator;
-use Illuminate\Support\Collection;
-use Illuminate\Support\Stringable;
-use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
-#[AsCommand(name: 'migrate:status')]
class StatusCommand extends BaseCommand
{
/**
@@ -35,7 +31,8 @@ class StatusCommand extends BaseCommand
/**
* Create a new migration rollback command instance.
*
- * @param \Illuminate\Database\Migrations\Migrator $migrator
+ * @param \Illuminate\Database\Migrations\Migrator $migrator
+ * @return \Illuminate\Database\Console\Migrations\StatusCommand
*/
public function __construct(Migrator $migrator)
{
@@ -47,82 +44,46 @@ public function __construct(Migrator $migrator)
/**
* Execute the console command.
*
- * @return int|null
+ * @return void
*/
- public function handle()
+ public function fire()
{
- 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;
- }
+ $this->migrator->setConnection($this->input->getOption('database'));
- $ran = $this->migrator->getRepository()->getRan();
+ if (! is_null($path = $this->input->getOption('path'))) {
+ $path = $this->laravel->basePath().'/'.$path;
+ } else {
+ $path = $this->getMigrationPath();
+ }
- $batches = $this->migrator->getRepository()->getMigrationBatches();
+ $ran = $this->migrator->getRepository()->getRan();
- $migrations = $this->getStatusFor($ran, $batches)
- ->when($this->option('pending') !== false, fn ($collection) => $collection->filter(function ($migration) {
- return (new Stringable($migration[1]))->contains('Pending');
- }));
+ $migrations = [];
- if (count($migrations) > 0) {
- $this->newLine();
+ foreach ($this->getAllMigrationFiles($path) as $migration) {
+ $migrations[] = in_array($migration, $ran) ? ['Y', $migration] : ['N', $migration];
+ }
- $this->components->twoColumnDetail('Migration name>', 'Batch / Status>');
-
- $migrations
- ->each(
- fn ($migration) => $this->components->twoColumnDetail($migration[0], $migration[1])
- );
-
- $this->newLine();
- } elseif ($this->option('pending') !== false) {
- $this->components->info('No pending migrations');
- } else {
- $this->components->info('No migrations found');
- }
-
- if ($this->option('pending') && $migrations->some(fn ($m) => (new Stringable($m[1]))->contains('Pending'))) {
- return $this->option('pending');
- }
- });
+ if (count($migrations) > 0) {
+ $this->table(['Ran?', 'Migration'], $migrations);
+ } else {
+ $this->error('No migrations found');
+ }
}
/**
- * Get the status for the given run migrations.
- *
- * @param array $ran
- * @param array $batches
- * @return \Illuminate\Support\Collection
- */
- protected function getStatusFor(array $ran, array $batches)
- {
- return (new Collection($this->getAllMigrationFiles()))
- ->map(function ($migration) use ($ran, $batches) {
- $migrationName = $this->migrator->getMigrationName($migration);
-
- $status = in_array($migrationName, $ran)
- ? 'Ran>'
- : 'Pending>';
-
- if (in_array($migrationName, $ran)) {
- $status = '['.$batches[$migrationName].'] '.$status;
- }
-
- return [$migrationName, $status];
- });
- }
-
- /**
- * Get an array of all of the migration files.
+ * Get all of the migration files.
*
+ * @param string $path
* @return array
*/
- protected function getAllMigrationFiles()
+ protected function getAllMigrationFiles($path)
{
- return $this->migrator->getMigrationFiles($this->getMigrationPaths());
+ return $this->migrator->getMigrationFiles($path);
}
/**
@@ -133,10 +94,9 @@ protected function getAllMigrationFiles()
protected function getOptions()
{
return [
- ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
- ['pending', null, InputOption::VALUE_OPTIONAL, 'Only list pending migrations', false],
- ['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'],
+ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'],
+
+ ['path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to use.'],
];
}
}
diff --git a/Console/Migrations/TableGuesser.php b/Console/Migrations/TableGuesser.php
deleted file mode 100644
index baf19485ee..0000000000
--- a/Console/Migrations/TableGuesser.php
+++ /dev/null
@@ -1,37 +0,0 @@
-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 (new Collection(explode(',', $databases)))->map(function ($database) {
- if (! $database) {
- $database = $this->laravel['config']['database.default'];
- }
-
- $maxConnections = $this->option('max');
-
- $connections = $this->connection->connection($database)->threadCount();
-
- return [
- 'database' => $database,
- 'connections' => $connections,
- 'status' => $maxConnections && $connections >= $maxConnections ? 'ALERT>' : '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('Database name>', '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'] === 'OK>') {
- return;
- }
-
- $this->events->dispatch(
- new DatabaseBusy(
- $database['database'],
- $database['connections']
- )
- );
- });
- }
-}
diff --git a/Console/PruneCommand.php b/Console/PruneCommand.php
deleted file mode 100644
index 527ab70c6f..0000000000
--- a/Console/PruneCommand.php
+++ /dev/null
@@ -1,199 +0,0 @@
-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");
- });
-
- $events->dispatch(new ModelPruningStarting($models->all()));
-
- $models->each(function ($model) {
- $this->pruneModel($model);
- });
-
- $events->dispatch(new ModelPruningFinished($models->all()));
-
- $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 = $model::isPrunable()
- ? $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
- *
- * @throws \InvalidArgumentException
- */
- protected function models()
- {
- $models = $this->option('model');
- $except = $this->option('except');
-
- if ($models && $except) {
- throw new InvalidArgumentException('The --models and --except options cannot be combined.');
- }
-
- if ($models) {
- return (new Collection($models))
- ->filter(static fn (string $model) => class_exists($model))
- ->values();
- }
-
- return (new Collection(Finder::create()->in($this->getPath())->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), fn ($models) => $models->reject(fn ($model) => in_array($model, $except)))
- ->filter(fn ($model) => $this->isPrunable($model))
- ->values();
- }
-
- /**
- * Get the path where models are located.
- *
- * @return string[]|string
- */
- protected function getPath()
- {
- if (! empty($path = $this->option('path'))) {
- return (new Collection($path))
- ->map(fn ($path) => base_path($path))
- ->all();
- }
-
- return app_path('Models');
- }
-
- /**
- * Display how many models will be pruned.
- *
- * @param class-string $model
- * @return void
- */
- protected function pretendToPrune($model)
- {
- $instance = new $model;
-
- $count = $instance->prunable()
- ->when($model::isSoftDeletable(), 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.");
- }
- }
-
- /**
- * Determine if the given model is prunable.
- *
- * @param string $model
- * @return bool
- */
- protected function isPrunable(string $model)
- {
- return class_exists($model)
- && is_a($model, Model::class, true)
- && ! (new \ReflectionClass($model))->isAbstract()
- && $model::isPrunable();
- }
-}
diff --git a/Console/Seeds/SeedCommand.php b/Console/Seeds/SeedCommand.php
index 515ff410b3..a505de06b2 100644
--- a/Console/Seeds/SeedCommand.php
+++ b/Console/Seeds/SeedCommand.php
@@ -3,18 +3,14 @@
namespace Illuminate\Database\Console\Seeds;
use Illuminate\Console\Command;
-use Illuminate\Console\ConfirmableTrait;
-use Illuminate\Console\Prohibitable;
-use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Model;
-use Symfony\Component\Console\Attribute\AsCommand;
-use Symfony\Component\Console\Input\InputArgument;
+use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Input\InputOption;
+use Illuminate\Database\ConnectionResolverInterface as Resolver;
-#[AsCommand(name: 'db:seed')]
class SeedCommand extends Command
{
- use ConfirmableTrait, Prohibitable;
+ use ConfirmableTrait;
/**
* The console command name.
@@ -41,6 +37,7 @@ class SeedCommand extends Command
* Create a new database seed command instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
+ * @return void
*/
public function __construct(Resolver $resolver)
{
@@ -52,30 +49,19 @@ public function __construct(Resolver $resolver)
/**
* Execute the console command.
*
- * @return int
+ * @return void
*/
- public function handle()
+ public function fire()
{
- if ($this->isProhibited() ||
- ! $this->confirmToProceed()) {
- return Command::FAILURE;
+ if (! $this->confirmToProceed()) {
+ return;
}
- $this->components->info('Seeding database.');
-
- $previousConnection = $this->resolver->getDefaultConnection();
-
$this->resolver->setDefaultConnection($this->getDatabase());
Model::unguarded(function () {
- $this->getSeeder()->__invoke();
+ $this->getSeeder()->run();
});
-
- if ($previousConnection) {
- $this->resolver->setDefaultConnection($previousConnection);
- }
-
- return 0;
}
/**
@@ -85,20 +71,9 @@ public function handle()
*/
protected function getSeeder()
{
- $class = $this->input->getArgument('class') ?? $this->input->getOption('class');
-
- if (! str_contains($class, '\\')) {
- $class = 'Database\\Seeders\\'.$class;
- }
-
- if ($class === 'Database\\Seeders\\DatabaseSeeder' &&
- ! class_exists($class)) {
- $class = 'DatabaseSeeder';
- }
+ $class = $this->laravel->make($this->input->getOption('class'));
- return $this->laravel->make($class)
- ->setContainer($this->laravel)
- ->setCommand($this);
+ return $class->setContainer($this->laravel)->setCommand($this);
}
/**
@@ -113,18 +88,6 @@ protected function getDatabase()
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.
*
@@ -133,9 +96,11 @@ protected function getArguments()
protected function getOptions()
{
return [
- ['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'],
+ ['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', '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.'],
];
}
}
diff --git a/Console/Seeds/SeederMakeCommand.php b/Console/Seeds/SeederMakeCommand.php
index c021bbbe54..3db79f1241 100644
--- a/Console/Seeds/SeederMakeCommand.php
+++ b/Console/Seeds/SeederMakeCommand.php
@@ -2,11 +2,10 @@
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
{
/**
@@ -31,36 +30,46 @@ class SeederMakeCommand extends GeneratorCommand
protected $type = 'Seeder';
/**
- * Execute the console command.
+ * 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 handle()
+ public function __construct(Filesystem $files, Composer $composer)
{
- parent::handle();
+ parent::__construct($files);
+
+ $this->composer = $composer;
}
/**
- * Get the stub file for the generator.
+ * Execute the console command.
*
- * @return string
+ * @return void
*/
- protected function getStub()
+ public function fire()
{
- return $this->resolveStubPath('/stubs/seeder.stub');
+ parent::fire();
+
+ $this->composer->dumpAutoloads();
}
/**
- * Resolve the fully-qualified path to the stub.
+ * Get the stub file for the generator.
*
- * @param string $stub
* @return string
*/
- protected function resolveStubPath($stub)
+ protected function getStub()
{
- return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
- ? $customPath
- : __DIR__.$stub;
+ return __DIR__.'/stubs/seeder.stub';
}
/**
@@ -71,22 +80,17 @@ protected function resolveStubPath($stub)
*/
protected function getPath($name)
{
- $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';
+ return $this->laravel->databasePath().'/seeds/'.$name.'.php';
}
/**
- * Get the root namespace for the class.
+ * Parse the name and format according to the root namespace.
*
+ * @param string $name
* @return string
*/
- protected function rootNamespace()
+ protected function parseName($name)
{
- return 'Database\Seeders\\';
+ return $name;
}
}
diff --git a/Console/Seeds/WithoutModelEvents.php b/Console/Seeds/WithoutModelEvents.php
deleted file mode 100644
index acd9ec3f20..0000000000
--- a/Console/Seeds/WithoutModelEvents.php
+++ /dev/null
@@ -1,19 +0,0 @@
- Model::withoutEvents($callback);
- }
-}
diff --git a/Console/Seeds/stubs/seeder.stub b/Console/Seeds/stubs/seeder.stub
index 8b5403f38d..4aa3845422 100644
--- a/Console/Seeds/stubs/seeder.stub
+++ b/Console/Seeds/stubs/seeder.stub
@@ -1,16 +1,15 @@
Note: This can be slow on large databases >}
- {--views : Show the database views Note: This can be slow on large databases >}
- {--types : Show the user defined types}';
-
- /**
- * 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)
- {
- $connection = $connections->connection($database = $this->input->getOption('database'));
-
- $schema = $connection->getSchemaBuilder();
-
- $data = [
- 'platform' => [
- 'config' => $this->getConfigFromDatabase($database),
- 'name' => $connection->getDriverTitle(),
- 'connection' => $connection->getName(),
- 'version' => $connection->getServerVersion(),
- 'open_connections' => $connection->threadCount(),
- ],
- 'tables' => $this->tables($connection, $schema),
- ];
-
- if ($this->option('views')) {
- $data['views'] = $this->views($connection, $schema);
- }
-
- if ($this->option('types')) {
- $data['types'] = $this->types($connection, $schema);
- }
-
- $this->display($data);
-
- return 0;
- }
-
- /**
- * Get information regarding the tables within the database.
- *
- * @param \Illuminate\Database\ConnectionInterface $connection
- * @param \Illuminate\Database\Schema\Builder $schema
- * @return \Illuminate\Support\Collection
- */
- protected function tables(ConnectionInterface $connection, Builder $schema)
- {
- return (new Collection($schema->getTables()))->map(fn ($table) => [
- 'table' => $table['name'],
- 'schema' => $table['schema'],
- 'schema_qualified_name' => $table['schema_qualified_name'],
- 'size' => $table['size'],
- 'rows' => $this->option('counts')
- ? $connection->withoutTablePrefix(fn ($connection) => $connection->table($table['schema_qualified_name'])->count())
- : null,
- 'engine' => $table['engine'],
- 'collation' => $table['collation'],
- 'comment' => $table['comment'],
- ]);
- }
-
- /**
- * Get information regarding the views within the database.
- *
- * @param \Illuminate\Database\ConnectionInterface $connection
- * @param \Illuminate\Database\Schema\Builder $schema
- * @return \Illuminate\Support\Collection
- */
- protected function views(ConnectionInterface $connection, Builder $schema)
- {
- return (new Collection($schema->getViews()))
- ->map(fn ($view) => [
- 'view' => $view['name'],
- 'schema' => $view['schema'],
- 'rows' => $connection->withoutTablePrefix(fn ($connection) => $connection->table($view['schema_qualified_name'])->count()),
- ]);
- }
-
- /**
- * Get information regarding the user-defined types within the database.
- *
- * @param \Illuminate\Database\ConnectionInterface $connection
- * @param \Illuminate\Database\Schema\Builder $schema
- * @return \Illuminate\Support\Collection
- */
- protected function types(ConnectionInterface $connection, Builder $schema)
- {
- return (new Collection($schema->getTypes()))
- ->map(fn ($type) => [
- 'name' => $type['name'],
- 'schema' => $type['schema'],
- 'type' => $type['type'],
- 'category' => $type['category'],
- ]);
- }
-
- /**
- * 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;
- $types = $data['types'] ?? null;
-
- $this->newLine();
-
- $this->components->twoColumnDetail(''.$platform['name'].'>', $platform['version']);
- $this->components->twoColumnDetail('Connection', $platform['connection']);
- $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::fileSize($tableSizeSum, 2));
- }
-
- $this->newLine();
-
- if ($tables->isNotEmpty()) {
- $hasSchema = ! is_null($tables->first()['schema']);
-
- $this->components->twoColumnDetail(
- ($hasSchema ? 'Schema> /> ' : '').'Table>',
- 'Size'.($this->option('counts') ? ' /> Rows>' : '')
- );
-
- $tables->each(function ($table) {
- $tableSize = is_null($table['size']) ? null : Number::fileSize($table['size'], 2);
-
- $this->components->twoColumnDetail(
- ($table['schema'] ? $table['schema'].' /> ' : '').$table['table'].($this->output->isVerbose() ? ' '.$table['engine'].'>' : null),
- ($tableSize ?? '—').($this->option('counts') ? ' /> '.Number::format($table['rows']).'>' : '')
- );
-
- if ($this->output->isVerbose()) {
- if ($table['comment']) {
- $this->components->bulletList([
- $table['comment'],
- ]);
- }
- }
- });
-
- $this->newLine();
- }
-
- if ($views && $views->isNotEmpty()) {
- $hasSchema = ! is_null($views->first()['schema']);
-
- $this->components->twoColumnDetail(
- ($hasSchema ? 'Schema> /> ' : '').'View>',
- 'Rows>'
- );
-
- $views->each(fn ($view) => $this->components->twoColumnDetail(
- ($view['schema'] ? $view['schema'].' /> ' : '').$view['view'],
- Number::format($view['rows'])
- ));
-
- $this->newLine();
- }
-
- if ($types && $types->isNotEmpty()) {
- $hasSchema = ! is_null($types->first()['schema']);
-
- $this->components->twoColumnDetail(
- ($hasSchema ? 'Schema> /> ' : '').'Type>',
- 'Type> /> Category>'
- );
-
- $types->each(fn ($type) => $this->components->twoColumnDetail(
- ($type['schema'] ? $type['schema'].' /> ' : '').$type['name'],
- $type['type'].' /> '.$type['category']
- ));
-
- $this->newLine();
- }
- }
-}
diff --git a/Console/ShowModelCommand.php b/Console/ShowModelCommand.php
deleted file mode 100644
index 463b0b73e6..0000000000
--- a/Console/ShowModelCommand.php
+++ /dev/null
@@ -1,191 +0,0 @@
-inspect(
- $this->argument('model'),
- $this->option('database')
- );
- } catch (BindingResolutionException $e) {
- $this->components->error($e->getMessage());
-
- return 1;
- }
-
- $this->display($info);
-
- return 0;
- }
-
- /**
- * Render the model information.
- *
- * @return void
- */
- protected function display(ModelInfo $modelData)
- {
- $this->option('json')
- ? $this->displayJson($modelData)
- : $this->displayCli($modelData);
- }
-
- /**
- * Render the model information as JSON.
- *
- * @return void
- */
- protected function displayJson(ModelInfo $modelData)
- {
- $this->output->writeln(
- (new Collection($modelData))->toJson()
- );
- }
-
- /**
- * Render the model information for the CLI.
- *
- * @return void
- */
- protected function displayCli(ModelInfo $modelData)
- {
- $this->newLine();
-
- $this->components->twoColumnDetail(''.$modelData->class.'>');
- $this->components->twoColumnDetail('Database', $modelData->database);
- $this->components->twoColumnDetail('Table', $modelData->table);
-
- if ($policy = $modelData->policy ?? false) {
- $this->components->twoColumnDetail('Policy', $policy);
- }
-
- $this->newLine();
-
- $this->components->twoColumnDetail(
- 'Attributes>',
- 'type /> cast>',
- );
-
- foreach ($modelData->attributes as $attribute) {
- $first = trim(sprintf(
- '%s %s',
- $attribute['name'],
- (new Collection(['increments', 'unique', 'nullable', 'fillable', 'hidden', 'appended']))
- ->filter(fn ($property) => $attribute[$property])
- ->map(fn ($property) => sprintf('%s>', $property))
- ->implode(',> ')
- ));
-
- $second = (new Collection([
- $attribute['type'],
- $attribute['cast'] ? ''.$attribute['cast'].'>' : null,
- ]))->filter()->implode(' /> ');
-
- $this->components->twoColumnDetail($first, $second);
-
- if ($attribute['default'] !== null) {
- $this->components->bulletList(
- [sprintf('default: %s', $attribute['default'])],
- OutputInterface::VERBOSITY_VERBOSE
- );
- }
- }
-
- $this->newLine();
-
- $this->components->twoColumnDetail('Relations>');
-
- foreach ($modelData->relations as $relation) {
- $this->components->twoColumnDetail(
- sprintf('%s %s>', $relation['name'], $relation['type']),
- $relation['related']
- );
- }
-
- $this->newLine();
-
- $this->components->twoColumnDetail('Events>');
-
- if ($modelData->events->count()) {
- foreach ($modelData->events as $event) {
- $this->components->twoColumnDetail(
- sprintf('%s', $event['event']),
- sprintf('%s', $event['class']),
- );
- }
- }
-
- $this->newLine();
-
- $this->components->twoColumnDetail('Observers>');
-
- if ($modelData->observers->count()) {
- foreach ($modelData->observers as $observer) {
- $this->components->twoColumnDetail(
- sprintf('%s', $observer['event']),
- implode(', ', $observer['observer'])
- );
- }
- }
-
- $this->newLine();
- }
-
- /**
- * Prompt for missing input arguments using the returned questions.
- *
- * @return array
- */
- protected function promptForMissingArgumentsUsing(): array
- {
- return [
- 'model' => fn (): string => suggest('Which model would you like to show?', $this->findAvailableModels()),
- ];
- }
-}
diff --git a/Console/TableCommand.php b/Console/TableCommand.php
deleted file mode 100644
index ecfa00a9e1..0000000000
--- a/Console/TableCommand.php
+++ /dev/null
@@ -1,283 +0,0 @@
-connection($this->input->getOption('database'));
- $tables = (new Collection($connection->getSchemaBuilder()->getTables()))
- ->keyBy('schema_qualified_name')->all();
-
- $tableNames = (new Collection($tables))->keys();
-
- $tableName = $this->argument('table') ?: search(
- 'Which table would you like to inspect?',
- fn (string $query) => $tableNames
- ->filter(fn ($table) => str_contains(strtolower($table), strtolower($query)))
- ->values()
- ->all()
- );
-
- $table = $tables[$tableName] ?? (new Collection($tables))->when(
- Arr::wrap($connection->getSchemaBuilder()->getCurrentSchemaListing()
- ?? $connection->getSchemaBuilder()->getCurrentSchemaName()),
- fn (Collection $collection, array $currentSchemas) => $collection->sortBy(
- function (array $table) use ($currentSchemas) {
- $index = array_search($table['schema'], $currentSchemas);
-
- return $index === false ? PHP_INT_MAX : $index;
- }
- )
- )->firstWhere('name', $tableName);
-
- if (! $table) {
- $this->components->warn("Table [{$tableName}] doesn't exist.");
-
- return 1;
- }
-
- [$columns, $indexes, $foreignKeys] = $connection->withoutTablePrefix(function ($connection) use ($table) {
- $schema = $connection->getSchemaBuilder();
- $tableName = $table['schema_qualified_name'];
-
- return [
- $this->columns($schema, $tableName),
- $this->indexes($schema, $tableName),
- $this->foreignKeys($schema, $tableName),
- ];
- });
-
- $data = [
- 'table' => [
- 'schema' => $table['schema'],
- 'name' => $table['name'],
- 'schema_qualified_name' => $table['schema_qualified_name'],
- 'columns' => count($columns),
- 'size' => $table['size'],
- 'comment' => $table['comment'],
- 'collation' => $table['collation'],
- 'engine' => $table['engine'],
- ],
- 'columns' => $columns,
- 'indexes' => $indexes,
- 'foreign_keys' => $foreignKeys,
- ];
-
- $this->display($data);
-
- return 0;
- }
-
- /**
- * Get the information regarding the table's columns.
- *
- * @param \Illuminate\Database\Schema\Builder $schema
- * @param string $table
- * @return \Illuminate\Support\Collection
- */
- protected function columns(Builder $schema, string $table)
- {
- return (new Collection($schema->getColumns($table)))->map(fn ($column) => [
- 'column' => $column['name'],
- 'attributes' => $this->getAttributesForColumn($column),
- 'default' => $column['default'],
- 'type' => $column['type'],
- ]);
- }
-
- /**
- * Get the attributes for a table column.
- *
- * @param array $column
- * @return \Illuminate\Support\Collection
- */
- protected function getAttributesForColumn($column)
- {
- return (new Collection([
- $column['type_name'],
- $column['generation'] ? $column['generation']['type'] : null,
- $column['auto_increment'] ? 'autoincrement' : null,
- $column['nullable'] ? 'nullable' : null,
- $column['collation'],
- ]))->filter();
- }
-
- /**
- * Get the information regarding the table's indexes.
- *
- * @param \Illuminate\Database\Schema\Builder $schema
- * @param string $table
- * @return \Illuminate\Support\Collection
- */
- protected function indexes(Builder $schema, string $table)
- {
- return (new Collection($schema->getIndexes($table)))->map(fn ($index) => [
- 'name' => $index['name'],
- 'columns' => new Collection($index['columns']),
- 'attributes' => $this->getAttributesForIndex($index),
- ]);
- }
-
- /**
- * Get the attributes for a table index.
- *
- * @param array $index
- * @return \Illuminate\Support\Collection
- */
- protected function getAttributesForIndex($index)
- {
- return (new Collection([
- $index['type'],
- count($index['columns']) > 1 ? 'compound' : null,
- $index['unique'] && ! $index['primary'] ? 'unique' : null,
- $index['primary'] ? 'primary' : null,
- ]))->filter();
- }
-
- /**
- * Get the information regarding the table's foreign keys.
- *
- * @param \Illuminate\Database\Schema\Builder $schema
- * @param string $table
- * @return \Illuminate\Support\Collection
- */
- protected function foreignKeys(Builder $schema, string $table)
- {
- return (new Collection($schema->getForeignKeys($table)))->map(fn ($foreignKey) => [
- 'name' => $foreignKey['name'],
- 'columns' => new Collection($foreignKey['columns']),
- 'foreign_schema' => $foreignKey['foreign_schema'],
- 'foreign_table' => $foreignKey['foreign_table'],
- 'foreign_columns' => new Collection($foreignKey['foreign_columns']),
- 'on_update' => $foreignKey['on_update'],
- 'on_delete' => $foreignKey['on_delete'],
- ]);
- }
-
- /**
- * 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(''.$table['schema_qualified_name'].'>', $table['comment'] ? ''.$table['comment'].'>' : null);
- $this->components->twoColumnDetail('Columns', $table['columns']);
-
- if (! is_null($table['size'])) {
- $this->components->twoColumnDetail('Size', Number::fileSize($table['size'], 2));
- }
-
- if ($table['engine']) {
- $this->components->twoColumnDetail('Engine', $table['engine']);
- }
-
- if ($table['collation']) {
- $this->components->twoColumnDetail('Collation', $table['collation']);
- }
-
- $this->newLine();
-
- if ($columns->isNotEmpty()) {
- $this->components->twoColumnDetail('Column>', 'Type');
-
- $columns->each(function ($column) {
- $this->components->twoColumnDetail(
- $column['column'].' '.$column['attributes']->implode(', ').'>',
- (! is_null($column['default']) ? ''.$column['default'].'> ' : '').$column['type']
- );
- });
-
- $this->newLine();
- }
-
- if ($indexes->isNotEmpty()) {
- $this->components->twoColumnDetail('Index>');
-
- $indexes->each(function ($index) {
- $this->components->twoColumnDetail(
- $index['name'].' '.$index['columns']->implode(', ').'>',
- $index['attributes']->implode(', ')
- );
- });
-
- $this->newLine();
- }
-
- if ($foreignKeys->isNotEmpty()) {
- $this->components->twoColumnDetail('Foreign Key>', 'On Update / On Delete');
-
- $foreignKeys->each(function ($foreignKey) {
- $this->components->twoColumnDetail(
- $foreignKey['name'].' '.$foreignKey['columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'>',
- $foreignKey['on_update'].' / '.$foreignKey['on_delete'],
- );
- });
-
- $this->newLine();
- }
- }
-}
diff --git a/Console/WipeCommand.php b/Console/WipeCommand.php
deleted file mode 100644
index d638db41d0..0000000000
--- a/Console/WipeCommand.php
+++ /dev/null
@@ -1,129 +0,0 @@
-isProhibited() ||
- ! $this->confirmToProceed()) {
- return Command::FAILURE;
- }
-
- $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.');
- }
-
- $this->flushDatabaseConnection($database);
-
- 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();
- }
-
- /**
- * Flush the given database connection.
- *
- * @param string $database
- * @return void
- */
- protected function flushDatabaseConnection($database)
- {
- $this->laravel['db']->connection($database)->disconnect();
- }
-
- /**
- * 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'],
- ];
- }
-}
diff --git a/DatabaseManager.php b/DatabaseManager.php
index 2d8edbc533..17f8bf676f 100755
--- a/DatabaseManager.php
+++ b/DatabaseManager.php
@@ -2,32 +2,18 @@
namespace Illuminate\Database;
-use Illuminate\Database\Connectors\ConnectionFactory;
-use Illuminate\Database\Events\ConnectionEstablished;
+use PDO;
use Illuminate\Support\Arr;
-use Illuminate\Support\Collection;
-use Illuminate\Support\ConfigurationUrlParser;
use Illuminate\Support\Str;
-use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
-use PDO;
-use RuntimeException;
-
-use function Illuminate\Support\enum_value;
+use Illuminate\Database\Connectors\ConnectionFactory;
-/**
- * @mixin \Illuminate\Database\Connection
- */
class DatabaseManager implements ConnectionResolverInterface
{
- use Macroable {
- __call as macroCall;
- }
-
/**
* The application instance.
*
- * @var \Illuminate\Contracts\Foundation\Application
+ * @var \Illuminate\Foundation\Application
*/
protected $app;
@@ -41,141 +27,124 @@ class DatabaseManager implements ConnectionResolverInterface
/**
* The active connection instances.
*
- * @var array
+ * @var array
*/
protected $connections = [];
- /**
- * The dynamically configured (DB::build) connection configurations.
- *
- * @var array
- */
- protected $dynamicConnectionConfigurations = [];
-
/**
* The custom connection resolvers.
*
- * @var array
+ * @var array
*/
protected $extensions = [];
- /**
- * The callback to be executed to reconnect to a database.
- *
- * @var callable
- */
- protected $reconnector;
-
/**
* Create a new database manager instance.
*
- * @param \Illuminate\Contracts\Foundation\Application $app
+ * @param \Illuminate\Foundation\Application $app
* @param \Illuminate\Database\Connectors\ConnectionFactory $factory
+ * @return void
*/
public function __construct($app, ConnectionFactory $factory)
{
$this->app = $app;
$this->factory = $factory;
-
- $this->reconnector = function ($connection) {
- $connection->setPdo(
- $this->reconnect($connection->getNameWithReadWriteType())->getRawPdo()
- );
- };
}
/**
* Get a database connection instance.
*
- * @param \UnitEnum|string|null $name
+ * @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
- [$database, $type] = $this->parseConnectionName($name = enum_value($name) ?: $this->getDefaultConnection());
+ list($name, $type) = $this->parseConnectionName($name);
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if (! isset($this->connections[$name])) {
- $this->connections[$name] = $this->configure(
- $this->makeConnection($database), $type
- );
+ $connection = $this->makeConnection($name);
+
+ $this->setPdoForType($connection, $type);
- $this->dispatchConnectionEstablishedEvent($this->connections[$name]);
+ $this->connections[$name] = $this->prepare($connection);
}
return $this->connections[$name];
}
/**
- * Build a database connection instance from the given configuration.
+ * Parse the connection into an array of the name and read / write type.
*
- * @param array $config
- * @return \Illuminate\Database\ConnectionInterface
+ * @param string $name
+ * @return array
*/
- public function build(array $config)
+ protected function parseConnectionName($name)
{
- $config['name'] ??= static::calculateDynamicConnectionName($config);
-
- $this->dynamicConnectionConfigurations[$config['name']] = $config;
+ $name = $name ?: $this->getDefaultConnection();
- return $this->connectUsing($config['name'], $config, true);
+ return Str::endsWith($name, ['::read', '::write'])
+ ? explode('::', $name, 2) : [$name, null];
}
/**
- * Calculate the dynamic connection name for an on-demand connection based on its configuration.
+ * Disconnect from the given database and remove from local cache.
*
- * @param array $config
- * @return string
+ * @param string $name
+ * @return void
*/
- public static function calculateDynamicConnectionName(array $config)
+ public function purge($name = null)
{
- return 'dynamic_'.md5((new Collection($config))->map(function ($value, $key) {
- return $key.(is_string($value) || is_int($value) ? $value : '');
- })->implode(''));
+ $this->disconnect($name);
+
+ unset($this->connections[$name]);
}
/**
- * Get a database connection instance from the given configuration.
- *
- * @param \UnitEnum|string $name
- * @param array $config
- * @param bool $force
- * @return \Illuminate\Database\ConnectionInterface
+ * Disconnect from the given database.
*
- * @throws \RuntimeException
+ * @param string $name
+ * @return void
*/
- public function connectUsing(string $name, array $config, bool $force = false)
+ public function disconnect($name = null)
{
- if ($force) {
- $this->purge($name = enum_value($name));
- }
-
- if (isset($this->connections[$name])) {
- throw new RuntimeException("Cannot establish connection [$name] because another connection with that name already exists.");
+ if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) {
+ $this->connections[$name]->disconnect();
}
+ }
- $connection = $this->configure(
- $this->factory->make($config, $name), null
- );
+ /**
+ * Reconnect to the given database.
+ *
+ * @param string $name
+ * @return \Illuminate\Database\Connection
+ */
+ public function reconnect($name = null)
+ {
+ $this->disconnect($name = $name ?: $this->getDefaultConnection());
- $this->dispatchConnectionEstablishedEvent($connection);
+ if (! isset($this->connections[$name])) {
+ return $this->connection($name);
+ }
- return tap($connection, fn ($connection) => $this->connections[$name] = $connection);
+ return $this->refreshPdoConnections($name);
}
/**
- * Parse the connection into an array of the name and read / write type.
+ * Refresh the PDO connections on a given connection.
*
* @param string $name
- * @return array
+ * @return \Illuminate\Database\Connection
*/
- protected function parseConnectionName($name)
+ protected function refreshPdoConnections($name)
{
- return Str::endsWith($name, ['::read', '::write'])
- ? explode('::', $name, 2)
- : [$name, null];
+ $fresh = $this->makeConnection($name);
+
+ return $this->connections[$name]
+ ->setPdo($fresh->getPdo())
+ ->setReadPdo($fresh->getReadPdo());
}
/**
@@ -186,7 +155,7 @@ protected function parseConnectionName($name)
*/
protected function makeConnection($name)
{
- $config = $this->configuration($name);
+ $config = $this->getConfig($name);
// First we will check by the connection name to see if an extension has been
// registered specifically for that connection. If it has we will call the
@@ -195,97 +164,54 @@ protected function makeConnection($name)
return call_user_func($this->extensions[$name], $config, $name);
}
+ $driver = $config['driver'];
+
// Next we will check to see if an extension has been registered for a driver
// and will call the Closure if so, which allows us to have a more generic
// resolver for the drivers themselves which applies to all connections.
- if (isset($this->extensions[$driver = $config['driver']])) {
+ if (isset($this->extensions[$driver])) {
return call_user_func($this->extensions[$driver], $config, $name);
}
return $this->factory->make($config, $name);
}
- /**
- * Get the configuration for a connection.
- *
- * @param string $name
- * @return array
- *
- * @throws \InvalidArgumentException
- */
- protected function configuration($name)
- {
- $connections = $this->app['config']['database.connections'];
-
- $config = $this->dynamicConnectionConfigurations[$name] ?? Arr::get($connections, $name);
-
- if (is_null($config)) {
- throw new InvalidArgumentException("Database connection [{$name}] not configured.");
- }
-
- return (new ConfigurationUrlParser)
- ->parseConfiguration($config);
- }
-
/**
* Prepare the database connection instance.
*
* @param \Illuminate\Database\Connection $connection
- * @param string $type
* @return \Illuminate\Database\Connection
*/
- protected function configure(Connection $connection, $type)
+ protected function prepare(Connection $connection)
{
- $connection = $this->setPdoForType($connection, $type)->setReadWriteType($type);
+ $connection->setFetchMode($this->app['config']['database.fetch']);
- // 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
- // used by the application. Once we're finished we'll return it back out.
if ($this->app->bound('events')) {
$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($this->reconnector);
+ $connection->setReconnector(function ($connection) {
+ $this->reconnect($connection->getName());
+ });
return $connection;
}
/**
- * Dispatch the ConnectionEstablished event if the event dispatcher is available.
- *
- * @param \Illuminate\Database\Connection $connection
- * @return void
- */
- protected function dispatchConnectionEstablishedEvent(Connection $connection)
- {
- if (! $this->app->bound('events')) {
- return;
- }
-
- $this->app['events']->dispatch(
- new ConnectionEstablished($connection)
- );
- }
-
- /**
- * Prepare the read / write mode for database connection instance.
+ * Prepare the read write mode for database connection instance.
*
* @param \Illuminate\Database\Connection $connection
- * @param string|null $type
+ * @param string $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());
}
@@ -293,87 +219,27 @@ protected function setPdoForType(Connection $connection, $type = null)
}
/**
- * Disconnect from the given database and remove from local cache.
- *
- * @param \UnitEnum|string|null $name
- * @return void
- */
- public function purge($name = null)
- {
- $this->disconnect($name = enum_value($name) ?: $this->getDefaultConnection());
-
- unset($this->connections[$name]);
- }
-
- /**
- * Disconnect from the given database.
- *
- * @param \UnitEnum|string|null $name
- * @return void
- */
- public function disconnect($name = null)
- {
- if (isset($this->connections[$name = enum_value($name) ?: $this->getDefaultConnection()])) {
- $this->connections[$name]->disconnect();
- }
- }
-
- /**
- * Reconnect to the given database.
+ * Get the configuration for a connection.
*
- * @param \UnitEnum|string|null $name
- * @return \Illuminate\Database\Connection
- */
- public function reconnect($name = null)
- {
- $this->disconnect($name = enum_value($name) ?: $this->getDefaultConnection());
-
- if (! isset($this->connections[$name])) {
- return $this->connection($name);
- }
-
- return tap($this->refreshPdoConnections($name), function ($connection) {
- $this->dispatchConnectionEstablishedEvent($connection);
- });
- }
-
- /**
- * Set the default database connection for the callback execution.
+ * @param string $name
+ * @return array
*
- * @param \UnitEnum|string $name
- * @param callable $callback
- * @return mixed
+ * @throws \InvalidArgumentException
*/
- public function usingConnection($name, callable $callback)
+ protected function getConfig($name)
{
- $previousName = $this->getDefaultConnection();
+ $name = $name ?: $this->getDefaultConnection();
- $this->setDefaultConnection($name = enum_value($name));
+ // To get the database connection configuration, we will just pull each of the
+ // connection configurations and get the configurations for the given name.
+ // If the configuration doesn't exist, we'll throw an exception and bail.
+ $connections = $this->app['config']['database.connections'];
- try {
- return $callback();
- } finally {
- $this->setDefaultConnection($previousName);
+ if (is_null($config = Arr::get($connections, $name))) {
+ throw new InvalidArgumentException("Database [$name] not configured.");
}
- }
-
- /**
- * Refresh the PDO connections on a given connection.
- *
- * @param string $name
- * @return \Illuminate\Database\Connection
- */
- protected function refreshPdoConnections($name)
- {
- [$database, $type] = $this->parseConnectionName($name);
-
- $fresh = $this->configure(
- $this->makeConnection($database), $type
- );
- return $this->connections[$name]
- ->setPdo($fresh->getRawPdo())
- ->setReadPdo($fresh->getRawReadPdo());
+ return $config;
}
/**
@@ -398,32 +264,29 @@ public function setDefaultConnection($name)
}
/**
- * Get all of the supported drivers.
+ * Get all of the support drivers.
*
- * @return string[]
+ * @return array
*/
public function supportedDrivers()
{
- return ['mysql', 'mariadb', 'pgsql', 'sqlite', 'sqlsrv'];
+ return ['mysql', 'pgsql', 'sqlite', 'sqlsrv'];
}
/**
* Get all of the drivers that are actually available.
*
- * @return string[]
+ * @return array
*/
public function availableDrivers()
{
- return array_intersect(
- $this->supportedDrivers(),
- str_replace('dblib', 'sqlsrv', PDO::getAvailableDrivers())
- );
+ return array_intersect($this->supportedDrivers(), str_replace('dblib', 'sqlsrv', PDO::getAvailableDrivers()));
}
/**
* Register an extension connection resolver.
*
- * @param string $name
+ * @param string $name
* @param callable $resolver
* @return void
*/
@@ -432,64 +295,25 @@ public function extend($name, callable $resolver)
$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
*/
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);
+ return call_user_func_array([$this->connection(), $method], $parameters);
}
}
diff --git a/DatabaseServiceProvider.php b/DatabaseServiceProvider.php
index 8a6115ba1e..1129baaaf7 100755
--- a/DatabaseServiceProvider.php
+++ b/DatabaseServiceProvider.php
@@ -4,23 +4,14 @@
use Faker\Factory as FakerFactory;
use Faker\Generator as FakerGenerator;
-use Illuminate\Contracts\Database\ConcurrencyErrorDetector as ConcurrencyErrorDetectorContract;
-use Illuminate\Contracts\Database\LostConnectionDetector as LostConnectionDetectorContract;
-use Illuminate\Contracts\Queue\EntityResolver;
-use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\QueueEntityResolver;
use Illuminate\Support\ServiceProvider;
+use Illuminate\Database\Eloquent\QueueEntityResolver;
+use Illuminate\Database\Connectors\ConnectionFactory;
+use Illuminate\Database\Eloquent\Factory as EloquentFactory;
class DatabaseServiceProvider extends ServiceProvider
{
- /**
- * The array of resolved Faker instances.
- *
- * @var array
- */
- protected static $fakers = [];
-
/**
* Bootstrap the application events.
*
@@ -42,18 +33,10 @@ public function register()
{
Model::clearBootedModels();
- $this->registerConnectionServices();
- $this->registerFakerGenerator();
+ $this->registerEloquentFactory();
+
$this->registerQueueableEntityResolver();
- }
- /**
- * Register the primary database bindings.
- *
- * @return void
- */
- protected function registerConnectionServices()
- {
// The connection factory is used to create the actual connection instances on
// the database. We will inject the factory into the manager so that it may
// make the connections while they are actually needed and not of before.
@@ -71,45 +54,23 @@ protected function registerConnectionServices()
$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 () {
- return new DatabaseTransactionsManager;
- });
-
- $this->app->singleton(ConcurrencyErrorDetectorContract::class, function () {
- return new ConcurrencyErrorDetector;
- });
-
- $this->app->singleton(LostConnectionDetectorContract::class, function () {
- return new LostConnectionDetector;
- });
}
/**
- * Register the Faker Generator instance in the container.
+ * Register the Eloquent factory instance in the container.
*
* @return void
*/
- protected function registerFakerGenerator()
+ protected function registerEloquentFactory()
{
- if (! class_exists(FakerGenerator::class)) {
- return;
- }
-
- $this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
- $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
-
- if (! isset(static::$fakers[$locale])) {
- static::$fakers[$locale] = FakerFactory::create($locale);
- }
+ $this->app->singleton(FakerGenerator::class, function () {
+ return FakerFactory::create();
+ });
- static::$fakers[$locale]->unique(true);
+ $this->app->singleton(EloquentFactory::class, function ($app) {
+ $faker = $app->make(FakerGenerator::class);
- return static::$fakers[$locale];
+ return EloquentFactory::construct($faker, database_path('factories'));
});
}
@@ -120,7 +81,7 @@ protected function registerFakerGenerator()
*/
protected function registerQueueableEntityResolver()
{
- $this->app->singleton(EntityResolver::class, function () {
+ $this->app->singleton('Illuminate\Contracts\Queue\EntityResolver', function () {
return new QueueEntityResolver;
});
}
diff --git a/DatabaseTransactionRecord.php b/DatabaseTransactionRecord.php
deleted file mode 100755
index 08fd471323..0000000000
--- a/DatabaseTransactionRecord.php
+++ /dev/null
@@ -1,121 +0,0 @@
-connection = $connection;
- $this->level = $level;
- $this->parent = $parent;
- }
-
- /**
- * Register a callback to be executed after committing.
- *
- * @param callable $callback
- * @return void
- */
- public function addCallback($callback)
- {
- $this->callbacks[] = $callback;
- }
-
- /**
- * Register a callback to be executed after rollback.
- *
- * @param callable $callback
- * @return void
- */
- public function addCallbackForRollback($callback)
- {
- $this->callbacksForRollback[] = $callback;
- }
-
- /**
- * Execute all of the callbacks.
- *
- * @return void
- */
- public function executeCallbacks()
- {
- foreach ($this->callbacks as $callback) {
- $callback();
- }
- }
-
- /**
- * Execute all of the callbacks for rollback.
- *
- * @return void
- */
- public function executeCallbacksForRollback()
- {
- foreach ($this->callbacksForRollback as $callback) {
- $callback();
- }
- }
-
- /**
- * Get all of the callbacks.
- *
- * @return array
- */
- public function getCallbacks()
- {
- return $this->callbacks;
- }
-
- /**
- * Get all of the callbacks for rollback.
- *
- * @return array
- */
- public function getCallbacksForRollback()
- {
- return $this->callbacksForRollback;
- }
-}
diff --git a/DatabaseTransactionsManager.php b/DatabaseTransactionsManager.php
deleted file mode 100755
index 9713c66d82..0000000000
--- a/DatabaseTransactionsManager.php
+++ /dev/null
@@ -1,267 +0,0 @@
-
- */
- protected $committedTransactions;
-
- /**
- * All of the pending transactions.
- *
- * @var \Illuminate\Support\Collection
- */
- protected $pendingTransactions;
-
- /**
- * The current transaction.
- *
- * @var array
- */
- protected $currentTransaction = [];
-
- /**
- * Create a new database transactions manager instance.
- */
- public function __construct()
- {
- $this->committedTransactions = new Collection;
- $this->pendingTransactions = new Collection;
- }
-
- /**
- * Start a new database transaction.
- *
- * @param string $connection
- * @param int $level
- * @return void
- */
- public function begin($connection, $level)
- {
- $this->pendingTransactions->push(
- $newTransaction = new DatabaseTransactionRecord(
- $connection,
- $level,
- $this->currentTransaction[$connection] ?? null
- )
- );
-
- $this->currentTransaction[$connection] = $newTransaction;
- }
-
- /**
- * Commit the root database transaction and execute callbacks.
- *
- * @param string $connection
- * @param int $levelBeingCommitted
- * @param int $newTransactionLevel
- * @return array
- */
- public function commit($connection, $levelBeingCommitted, $newTransactionLevel)
- {
- $this->stageTransactions($connection, $levelBeingCommitted);
-
- if (isset($this->currentTransaction[$connection])) {
- $this->currentTransaction[$connection] = $this->currentTransaction[$connection]->parent;
- }
-
- if (! $this->afterCommitCallbacksShouldBeExecuted($newTransactionLevel) &&
- $newTransactionLevel !== 0) {
- return [];
- }
-
- // This method is only called when the root database transaction is committed so there
- // shouldn't be any pending transactions, but going to clear them here anyways just
- // in case. This method could be refactored to receive a level in the future too.
- $this->pendingTransactions = $this->pendingTransactions->reject(
- fn ($transaction) => $transaction->connection === $connection &&
- $transaction->level >= $levelBeingCommitted
- )->values();
-
- [$forThisConnection, $forOtherConnections] = $this->committedTransactions->partition(
- fn ($transaction) => $transaction->connection == $connection
- );
-
- $this->committedTransactions = $forOtherConnections->values();
-
- $forThisConnection->map->executeCallbacks();
-
- return $forThisConnection;
- }
-
- /**
- * Move relevant pending transactions to a committed state.
- *
- * @param string $connection
- * @param int $levelBeingCommitted
- * @return void
- */
- public function stageTransactions($connection, $levelBeingCommitted)
- {
- $this->committedTransactions = $this->committedTransactions->merge(
- $this->pendingTransactions->filter(
- fn ($transaction) => $transaction->connection === $connection &&
- $transaction->level >= $levelBeingCommitted
- )
- );
-
- $this->pendingTransactions = $this->pendingTransactions->reject(
- fn ($transaction) => $transaction->connection === $connection &&
- $transaction->level >= $levelBeingCommitted
- );
- }
-
- /**
- * Rollback the active database transaction.
- *
- * @param string $connection
- * @param int $newTransactionLevel
- * @return void
- */
- public function rollback($connection, $newTransactionLevel)
- {
- if ($newTransactionLevel === 0) {
- $this->removeAllTransactionsForConnection($connection);
- } else {
- $this->pendingTransactions = $this->pendingTransactions->reject(
- fn ($transaction) => $transaction->connection == $connection &&
- $transaction->level > $newTransactionLevel
- )->values();
-
- if ($this->currentTransaction) {
- do {
- $this->removeCommittedTransactionsThatAreChildrenOf($this->currentTransaction[$connection]);
-
- $this->currentTransaction[$connection]->executeCallbacksForRollback();
-
- $this->currentTransaction[$connection] = $this->currentTransaction[$connection]->parent;
- } while (
- isset($this->currentTransaction[$connection]) &&
- $this->currentTransaction[$connection]->level > $newTransactionLevel
- );
- }
- }
- }
-
- /**
- * Remove all pending, completed, and current transactions for the given connection name.
- *
- * @param string $connection
- * @return void
- */
- protected function removeAllTransactionsForConnection($connection)
- {
- if ($this->currentTransaction) {
- for ($currentTransaction = $this->currentTransaction[$connection]; isset($currentTransaction); $currentTransaction = $currentTransaction->parent) {
- $currentTransaction->executeCallbacksForRollback();
- }
- }
-
- $this->currentTransaction[$connection] = null;
-
- $this->pendingTransactions = $this->pendingTransactions->reject(
- fn ($transaction) => $transaction->connection == $connection
- )->values();
-
- $this->committedTransactions = $this->committedTransactions->reject(
- fn ($transaction) => $transaction->connection == $connection
- )->values();
- }
-
- /**
- * Remove all transactions that are children of the given transaction.
- *
- * @param \Illuminate\Database\DatabaseTransactionRecord $transaction
- * @return void
- */
- protected function removeCommittedTransactionsThatAreChildrenOf(DatabaseTransactionRecord $transaction)
- {
- [$removedTransactions, $this->committedTransactions] = $this->committedTransactions->partition(
- fn ($committed) => $committed->connection == $transaction->connection &&
- $committed->parent === $transaction
- );
-
- // There may be multiple deeply nested transactions that have already committed that we
- // also need to remove. We will recurse down the children of all removed transaction
- // instances until there are no more deeply nested child transactions for removal.
- $removedTransactions->each(
- fn ($transaction) => $this->removeCommittedTransactionsThatAreChildrenOf($transaction)
- );
- }
-
- /**
- * Register a transaction callback.
- *
- * @param callable $callback
- * @return void
- */
- public function addCallback($callback)
- {
- if ($current = $this->callbackApplicableTransactions()->last()) {
- return $current->addCallback($callback);
- }
-
- $callback();
- }
-
- /**
- * Register a callback for transaction rollback.
- *
- * @param callable $callback
- * @return void
- */
- public function addCallbackForRollback($callback)
- {
- if ($current = $this->callbackApplicableTransactions()->last()) {
- return $current->addCallbackForRollback($callback);
- }
- }
-
- /**
- * Get the transactions that are applicable to callbacks.
- *
- * @return \Illuminate\Support\Collection
- */
- public function callbackApplicableTransactions()
- {
- return $this->pendingTransactions;
- }
-
- /**
- * Determine if after commit callbacks should be executed for the given transaction level.
- *
- * @param int $level
- * @return bool
- */
- public function afterCommitCallbacksShouldBeExecuted($level)
- {
- return $level === 0;
- }
-
- /**
- * Get all of the pending transactions.
- *
- * @return \Illuminate\Support\Collection
- */
- public function getPendingTransactions()
- {
- return $this->pendingTransactions;
- }
-
- /**
- * Get all of the committed transactions.
- *
- * @return \Illuminate\Support\Collection
- */
- public function getCommittedTransactions()
- {
- return $this->committedTransactions;
- }
-}
diff --git a/DeadlockException.php b/DeadlockException.php
deleted file mode 100644
index 375a39bc96..0000000000
--- a/DeadlockException.php
+++ /dev/null
@@ -1,10 +0,0 @@
-bound(ConcurrencyErrorDetectorContract::class)
- ? $container[ConcurrencyErrorDetectorContract::class]
- : new ConcurrencyErrorDetector();
-
- return $detector->causedByConcurrencyError($e);
- }
-}
diff --git a/DetectsLostConnections.php b/DetectsLostConnections.php
index ba649afe2a..6c3f69838c 100644
--- a/DetectsLostConnections.php
+++ b/DetectsLostConnections.php
@@ -2,26 +2,33 @@
namespace Illuminate\Database;
-use Illuminate\Container\Container;
-use Illuminate\Contracts\Database\LostConnectionDetector as LostConnectionDetectorContract;
-use Throwable;
+use Exception;
+use Illuminate\Support\Str;
trait DetectsLostConnections
{
/**
* Determine if the given exception was caused by a lost connection.
*
- * @param \Throwable $e
+ * @param \Exception $e
* @return bool
*/
- protected function causedByLostConnection(Throwable $e)
+ protected function causedByLostConnection(Exception $e)
{
- $container = Container::getInstance();
+ $message = $e->getMessage();
- $detector = $container->bound(LostConnectionDetectorContract::class)
- ? $container[LostConnectionDetectorContract::class]
- : new LostConnectionDetector();
-
- return $detector->causedByLostConnection($e);
+ return Str::contains($message, [
+ 'server has gone away',
+ 'no connection to the server',
+ 'Lost connection',
+ 'is dead or not enabled',
+ 'Error while sending',
+ 'decryption failed or bad record mac',
+ 'server closed the connection unexpectedly',
+ 'SSL connection has been closed unexpectedly',
+ 'Deadlock found when trying to get lock',
+ 'Error writing data to the connection',
+ 'Resource deadlock avoided',
+ ]);
}
}
diff --git a/Eloquent/Attributes/Appends.php b/Eloquent/Attributes/Appends.php
deleted file mode 100644
index 6b696e3123..0000000000
--- a/Eloquent/Attributes/Appends.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $columns
- */
- public function __construct(public array $columns)
- {
- }
-}
diff --git a/Eloquent/Attributes/Boot.php b/Eloquent/Attributes/Boot.php
deleted file mode 100644
index f57da7af94..0000000000
--- a/Eloquent/Attributes/Boot.php
+++ /dev/null
@@ -1,11 +0,0 @@
-> $collectionClass
- */
- public function __construct(public string $collectionClass)
- {
- }
-}
diff --git a/Eloquent/Attributes/Connection.php b/Eloquent/Attributes/Connection.php
deleted file mode 100644
index d02fcc502f..0000000000
--- a/Eloquent/Attributes/Connection.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $columns
- */
- public function __construct(public array $columns)
- {
- }
-}
diff --git a/Eloquent/Attributes/Guarded.php b/Eloquent/Attributes/Guarded.php
deleted file mode 100644
index d2f9c34e8d..0000000000
--- a/Eloquent/Attributes/Guarded.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $columns
- */
- public function __construct(public array $columns)
- {
- }
-}
diff --git a/Eloquent/Attributes/Hidden.php b/Eloquent/Attributes/Hidden.php
deleted file mode 100644
index a7dbfbc446..0000000000
--- a/Eloquent/Attributes/Hidden.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $columns
- */
- public function __construct(public array $columns)
- {
- }
-}
diff --git a/Eloquent/Attributes/Initialize.php b/Eloquent/Attributes/Initialize.php
deleted file mode 100644
index 58f48769eb..0000000000
--- a/Eloquent/Attributes/Initialize.php
+++ /dev/null
@@ -1,11 +0,0 @@
- $relations
- */
- public function __construct(public array $relations)
- {
- }
-}
diff --git a/Eloquent/Attributes/Unguarded.php b/Eloquent/Attributes/Unguarded.php
deleted file mode 100644
index 005105e47e..0000000000
--- a/Eloquent/Attributes/Unguarded.php
+++ /dev/null
@@ -1,11 +0,0 @@
- $builderClass
- */
- public function __construct(public string $builderClass)
- {
- }
-}
diff --git a/Eloquent/Attributes/UseFactory.php b/Eloquent/Attributes/UseFactory.php
deleted file mode 100644
index a013102fcd..0000000000
--- a/Eloquent/Attributes/UseFactory.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $factoryClass
- */
- public function __construct(public string $factoryClass)
- {
- }
-}
diff --git a/Eloquent/Attributes/UsePolicy.php b/Eloquent/Attributes/UsePolicy.php
deleted file mode 100644
index 9306598e07..0000000000
--- a/Eloquent/Attributes/UsePolicy.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $class
- */
- public function __construct(public string $class)
- {
- }
-}
diff --git a/Eloquent/Attributes/UseResource.php b/Eloquent/Attributes/UseResource.php
deleted file mode 100644
index a1cbc48f3a..0000000000
--- a/Eloquent/Attributes/UseResource.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $class
- */
- public function __construct(public string $class)
- {
- }
-}
diff --git a/Eloquent/Attributes/UseResourceCollection.php b/Eloquent/Attributes/UseResourceCollection.php
deleted file mode 100644
index c17e1f1768..0000000000
--- a/Eloquent/Attributes/UseResourceCollection.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $class
- */
- public function __construct(public string $class)
- {
- }
-}
diff --git a/Eloquent/Attributes/Visible.php b/Eloquent/Attributes/Visible.php
deleted file mode 100644
index b9c87575a4..0000000000
--- a/Eloquent/Attributes/Visible.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $columns
- */
- public function __construct(public array $columns)
- {
- }
-}
diff --git a/Eloquent/BroadcastableModelEventOccurred.php b/Eloquent/BroadcastableModelEventOccurred.php
deleted file mode 100644
index 8bd028032e..0000000000
--- a/Eloquent/BroadcastableModelEventOccurred.php
+++ /dev/null
@@ -1,144 +0,0 @@
-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 (new BaseCollection($channels))
- ->map(fn ($channel) => $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;
- }
-}
diff --git a/Eloquent/BroadcastsEvents.php b/Eloquent/BroadcastsEvents.php
deleted file mode 100644
index c0461ddb0a..0000000000
--- a/Eloquent/BroadcastsEvents.php
+++ /dev/null
@@ -1,197 +0,0 @@
-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(string $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;
- }
-}
diff --git a/Eloquent/BroadcastsEventsAfterCommit.php b/Eloquent/BroadcastsEventsAfterCommit.php
deleted file mode 100644
index 806af70f87..0000000000
--- a/Eloquent/BroadcastsEventsAfterCommit.php
+++ /dev/null
@@ -1,18 +0,0 @@
- */
- use BuildsQueries, ForwardsCalls, QueriesRelationships {
- BuildsQueries::sole as baseSole;
- }
+use Illuminate\Pagination\Paginator;
+use Illuminate\Database\Query\Expression;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Database\Eloquent\Relations\Relation;
+use Illuminate\Database\Query\Builder as QueryBuilder;
+class Builder
+{
/**
* The base query builder instance.
*
@@ -49,17 +23,10 @@ class Builder implements BuilderContract
/**
* The model being queried.
*
- * @var TModel
+ * @var \Illuminate\Database\Eloquent\Model
*/
protected $model;
- /**
- * The attributes that should be added to new models created by this builder.
- *
- * @var array
- */
- public $pendingAttributes = [];
-
/**
* The relationships that should be eager loaded.
*
@@ -68,18 +35,11 @@ class Builder implements BuilderContract
protected $eagerLoad = [];
/**
- * All of the globally registered builder macros.
- *
- * @var array
- */
- protected static $macros = [];
-
- /**
- * All of the locally registered builder macros.
+ * All of the registered builder macros.
*
* @var array
*/
- protected $localMacros = [];
+ protected $macros = [];
/**
* A replacement for the typical delete function.
@@ -88,53 +48,14 @@ class Builder implements BuilderContract
*/
protected $onDelete;
- /**
- * The properties that should be returned from query builder.
- *
- * @var string[]
- */
- protected $propertyPassthru = [
- 'from',
- ];
-
/**
* The methods that should be returned from query builder.
*
- * @var string[]
+ * @var array
*/
protected $passthru = [
- 'aggregate',
- 'average',
- 'avg',
- 'count',
- 'dd',
- 'ddrawsql',
- 'doesntexist',
- 'doesntexistor',
- 'dump',
- 'dumprawsql',
- 'exists',
- 'existsor',
- 'explain',
- 'getbindings',
- 'getconnection',
- 'getcountforpagination',
- 'getgrammar',
- 'getrawbindings',
- 'implode',
- 'insert',
- 'insertgetid',
- 'insertorignore',
- 'insertusing',
- 'insertorignoreusing',
- 'max',
- 'min',
- 'numericaggregate',
- 'raw',
- 'rawvalue',
- 'sum',
- 'tosql',
- 'torawsql',
+ 'insert', 'insertGetId', 'getBindings', 'toSql',
+ 'exists', 'count', 'min', 'max', 'avg', 'sum', 'getConnection',
];
/**
@@ -151,41 +72,17 @@ class Builder implements BuilderContract
*/
protected $removedScopes = [];
- /**
- * The callbacks that should be invoked after retrieving data from the database.
- *
- * @var array
- */
- protected $afterQueryCallbacks = [];
-
- /**
- * The callbacks that should be invoked on clone.
- *
- * @var array
- */
- protected $onCloneCallbacks = [];
-
/**
* Create a new Eloquent query builder instance.
*
* @param \Illuminate\Database\Query\Builder $query
+ * @return void
*/
public function __construct(QueryBuilder $query)
{
$this->query = $query;
}
- /**
- * Create and return an un-saved model instance.
- *
- * @param array $attributes
- * @return TModel
- */
- public function make(array $attributes = [])
- {
- return $this->newModelInstance($attributes);
- }
-
/**
* Register a new global scope.
*
@@ -229,34 +126,19 @@ public function withoutGlobalScope($scope)
* @param array|null $scopes
* @return $this
*/
- public function withoutGlobalScopes(?array $scopes = null)
+ public function withoutGlobalScopes(array $scopes = null)
{
- if (! is_array($scopes)) {
- $scopes = array_keys($this->scopes);
- }
-
- foreach ($scopes as $scope) {
- $this->withoutGlobalScope($scope);
+ if (is_array($scopes)) {
+ foreach ($scopes as $scope) {
+ $this->withoutGlobalScope($scope);
+ }
+ } else {
+ $this->scopes = [];
}
return $this;
}
- /**
- * Remove all global scopes except the given scopes.
- *
- * @param array $scopes
- * @return $this
- */
- public function withoutGlobalScopesExcept(array $scopes = [])
- {
- $this->withoutGlobalScopes(
- array_diff(array_keys($this->scopes), $scopes)
- );
-
- return $this;
- }
-
/**
* Get an array of global scopes that were removed from the query.
*
@@ -268,651 +150,459 @@ public function removedScopes()
}
/**
- * Add a where clause on the primary key to the query.
+ * Find a model by its primary key.
*
* @param mixed $id
- * @return $this
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null
*/
- public function whereKey($id)
+ public function find($id, $columns = ['*'])
{
- if ($id instanceof Model) {
- $id = $id->getKey();
+ if (is_array($id)) {
+ return $this->findMany($id, $columns);
}
- if (is_array($id) || $id instanceof Arrayable) {
- if (in_array($this->model->getKeyType(), ['int', 'integer'])) {
- $this->query->whereIntegerInRaw($this->model->getQualifiedKeyName(), $id);
- } else {
- $this->query->whereIn($this->model->getQualifiedKeyName(), $id);
- }
+ $this->query->where($this->model->getQualifiedKeyName(), '=', $id);
- return $this;
- }
+ return $this->first($columns);
+ }
- if ($id !== null && $this->model->getKeyType() === 'string') {
- $id = (string) $id;
+ /**
+ * Find multiple models by their primary keys.
+ *
+ * @param array $ids
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ public function findMany($ids, $columns = ['*'])
+ {
+ if (empty($ids)) {
+ return $this->model->newCollection();
}
- return $this->where($this->model->getQualifiedKeyName(), '=', $id);
+ $this->query->whereIn($this->model->getQualifiedKeyName(), $ids);
+
+ return $this->get($columns);
}
/**
- * Add a where clause on the primary key to the query.
+ * Find a model by its primary key or throw an exception.
*
* @param mixed $id
- * @return $this
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
+ *
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
- public function whereKeyNot($id)
+ public function findOrFail($id, $columns = ['*'])
{
- if ($id instanceof Model) {
- $id = $id->getKey();
- }
+ $result = $this->find($id, $columns);
- if (is_array($id) || $id instanceof Arrayable) {
- if (in_array($this->model->getKeyType(), ['int', 'integer'])) {
- $this->query->whereIntegerNotInRaw($this->model->getQualifiedKeyName(), $id);
- } else {
- $this->query->whereNotIn($this->model->getQualifiedKeyName(), $id);
+ if (is_array($id)) {
+ if (count($result) == count(array_unique($id))) {
+ return $result;
}
-
- return $this;
- }
-
- if ($id !== null && $this->model->getKeyType() === 'string') {
- $id = (string) $id;
+ } elseif (! is_null($result)) {
+ return $result;
}
- return $this->where($this->model->getQualifiedKeyName(), '!=', $id);
+ throw (new ModelNotFoundException)->setModel(get_class($this->model));
}
/**
- * Exclude the given models from the query results.
+ * Find a model by its primary key or return fresh model instance.
*
- * @param iterable|mixed $models
- * @return static
+ * @param mixed $id
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Model
*/
- public function except($models)
+ public function findOrNew($id, $columns = ['*'])
{
- return $this->whereKeyNot(
- $models instanceof Model
- ? $models->getKey()
- : Collection::wrap($models)->modelKeys()
- );
+ if (! is_null($model = $this->find($id, $columns))) {
+ return $model;
+ }
+
+ return $this->model->newInstance();
}
/**
- * Add a basic where clause to the query.
+ * Get the first record matching the attributes or instantiate it.
*
- * @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @param string $boolean
- * @return $this
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
*/
- public function where($column, $operator = null, $value = null, $boolean = 'and')
+ public function firstOrNew(array $attributes)
{
- if ($column instanceof Closure && is_null($operator)) {
- $column($query = $this->model->newQueryWithoutRelationships());
-
- $this->eagerLoad = array_merge($this->eagerLoad, $query->getEagerLoads());
-
- $this->withoutGlobalScopes(
- $query->removedScopes()
- );
- $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
- } else {
- $this->query->where(...func_get_args());
+ if (! is_null($instance = $this->where($attributes)->first())) {
+ return $instance;
}
- return $this;
+ return $this->model->newInstance($attributes);
}
/**
- * Add a basic where clause to the query, and return the first result.
+ * Get the first record matching the attributes or create it.
*
- * @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @param string $boolean
- * @return TModel|null
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
*/
- public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
+ public function firstOrCreate(array $attributes, array $values = [])
{
- return $this->where(...func_get_args())->first();
+ if (! is_null($instance = $this->where($attributes)->first())) {
+ return $instance;
+ }
+
+ $instance = $this->model->newInstance($attributes + $values);
+
+ $instance->save();
+
+ return $instance;
}
/**
- * Add an "or where" clause to the query.
+ * Create or update a record matching the attributes, and fill it with values.
*
- * @param (\Closure(static): mixed)|array|string|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @return $this
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
*/
- public function orWhere($column, $operator = null, $value = null)
+ public function updateOrCreate(array $attributes, array $values = [])
{
- [$value, $operator] = $this->query->prepareValueAndOperator(
- $value, $operator, func_num_args() === 2
- );
+ $instance = $this->firstOrNew($attributes);
- return $this->where($column, $operator, $value, 'or');
+ $instance->fill($values)->save();
+
+ return $instance;
}
/**
- * Add a basic "where not" clause to the query.
+ * Execute the query and get the first result.
*
- * @param (\Closure(static): mixed)|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @param string $boolean
- * @return $this
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Model|static|null
*/
- public function whereNot($column, $operator = null, $value = null, $boolean = 'and')
+ public function first($columns = ['*'])
{
- return $this->where($column, $operator, $value, $boolean.' not');
+ return $this->take(1)->get($columns)->first();
}
/**
- * Add an "or where not" clause to the query.
+ * Execute the query and get the first result or throw an exception.
*
- * @param (\Closure(static): mixed)|array|string|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @return $this
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Model|static
+ *
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
- public function orWhereNot($column, $operator = null, $value = null)
+ public function firstOrFail($columns = ['*'])
{
- return $this->whereNot($column, $operator, $value, 'or');
+ if (! is_null($model = $this->first($columns))) {
+ return $model;
+ }
+
+ throw (new ModelNotFoundException)->setModel(get_class($this->model));
}
/**
- * Add an "order by" clause for a timestamp to the query.
+ * Execute the query as a "select" statement.
*
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @return $this
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Collection|static[]
*/
- public function latest($column = null)
+ public function get($columns = ['*'])
{
- if (is_null($column)) {
- $column = $this->model->getCreatedAtColumn() ?? 'created_at';
- }
+ $builder = $this->applyScopes();
- $this->query->latest($column);
+ $models = $builder->getModels($columns);
- return $this;
+ // If we actually found models we will also eager load any relationships that
+ // have been specified as needing to be eager loaded, which will solve the
+ // n+1 query issue for the developers to avoid running a lot of queries.
+ if (count($models) > 0) {
+ $models = $builder->eagerLoadRelations($models);
+ }
+
+ return $builder->getModel()->newCollection($models);
}
/**
- * Add an "order by" clause for a timestamp to the query.
+ * Get a single column's value from the first result of a query.
*
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @return $this
+ * @param string $column
+ * @return mixed
*/
- public function oldest($column = null)
+ public function value($column)
{
- if (is_null($column)) {
- $column = $this->model->getCreatedAtColumn() ?? 'created_at';
- }
+ $result = $this->first([$column]);
- $this->query->oldest($column);
-
- return $this;
+ if ($result) {
+ return $result->{$column};
+ }
}
/**
- * Create a collection of models from plain arrays.
+ * Chunk the results of the query.
*
- * @param array $items
- * @return \Illuminate\Database\Eloquent\Collection
+ * @param int $count
+ * @param callable $callback
+ * @return bool
*/
- public function hydrate(array $items)
+ public function chunk($count, callable $callback)
{
- $instance = $this->newModelInstance();
+ $results = $this->forPage($page = 1, $count)->get();
- return $instance->newCollection(array_map(function ($item) use ($items, $instance) {
- $model = $instance->newFromBuilder($item);
-
- if (count($items) > 1) {
- $model->preventsLazyLoading = Model::preventsLazyLoading();
+ while (! $results->isEmpty()) {
+ // 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 (call_user_func($callback, $results) === false) {
+ return false;
}
- return $model;
- }, $items));
+ $page++;
+
+ $results = $this->forPage($page, $count)->get();
+ }
+
+ return true;
}
/**
- * Insert into the database after merging the model's default attributes, setting timestamps, and casting values.
+ * Chunk the results of a query by comparing numeric IDs.
*
- * @param array> $values
+ * @param int $count
+ * @param callable $callback
+ * @param string $column
* @return bool
*/
- public function fillAndInsert(array $values)
+ public function chunkById($count, callable $callback, $column = 'id')
{
- return $this->insert($this->fillForInsert($values));
- }
+ $lastId = null;
- /**
- * Insert (ignoring errors) into the database after merging the model's default attributes, setting timestamps, and casting values.
- *
- * @param array> $values
- * @return int
- */
- public function fillAndInsertOrIgnore(array $values)
- {
- return $this->insertOrIgnore($this->fillForInsert($values));
+ $results = $this->forPageAfterId($count, 0, $column)->get();
+
+ while (! $results->isEmpty()) {
+ if (call_user_func($callback, $results) === false) {
+ return false;
+ }
+
+ $lastId = $results->last()->{$column};
+
+ $results = $this->forPageAfterId($count, $lastId, $column)->get();
+ }
+
+ return true;
}
/**
- * Insert a record into the database and get its ID after merging the model's default attributes, setting timestamps, and casting values.
+ * Execute a callback over each item while chunking.
*
- * @param array $values
- * @return int
+ * @param callable $callback
+ * @param int $count
+ * @return bool
*/
- public function fillAndInsertGetId(array $values)
+ public function each(callable $callback, $count = 1000)
{
- return $this->insertGetId($this->fillForInsert([$values])[0]);
+ if (is_null($this->query->orders) && is_null($this->query->unionOrders)) {
+ $this->orderBy($this->model->getQualifiedKeyName(), 'asc');
+ }
+
+ return $this->chunk($count, function ($results) use ($callback) {
+ foreach ($results as $key => $value) {
+ if ($callback($value, $key) === false) {
+ return false;
+ }
+ }
+ });
}
/**
- * Enrich the given values by merging in the model's default attributes, adding timestamps, and casting values.
+ * Get an array with the values of a given column.
*
- * @param array> $values
- * @return array>
+ * @param string $column
+ * @param string|null $key
+ * @return \Illuminate\Support\Collection
*/
- public function fillForInsert(array $values)
+ public function pluck($column, $key = null)
{
- if (empty($values)) {
- return [];
- }
+ $results = $this->toBase()->pluck($column, $key);
- if (! is_array(array_first($values))) {
- $values = [$values];
+ // If the model has a mutator for the requested column, we will spin through
+ // the results and mutate the values so that the mutated version of these
+ // columns are returned as you would expect from these Eloquent models.
+ if (! $this->model->hasGetMutator($column)) {
+ return $results;
}
- $this->model->unguarded(function () use (&$values) {
- foreach ($values as $key => $rowValues) {
- $values[$key] = tap(
- $this->newModelInstance($rowValues),
- fn ($model) => $model->setUniqueIds()
- )->getAttributes();
- }
+ return $results->map(function ($value) use ($column) {
+ return $this->model->newFromBuilder([$column => $value])->$column;
});
-
- return $this->addTimestampsToUpsertValues($values);
}
/**
- * Create a collection of models from a raw query.
+ * Paginate the given query.
+ *
+ * @param int $perPage
+ * @param array $columns
+ * @param string $pageName
+ * @param int|null $page
+ * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*
- * @param string $query
- * @param array $bindings
- * @return \Illuminate\Database\Eloquent\Collection
+ * @throws \InvalidArgumentException
*/
- public function fromQuery($query, $bindings = [])
+ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
- return $this->hydrate(
- $this->query->getConnection()->select($query, $bindings)
+ $query = $this->toBase();
+
+ $total = $query->getCountForPagination();
+
+ $this->forPage(
+ $page = $page ?: Paginator::resolveCurrentPage($pageName),
+ $perPage = $perPage ?: $this->model->getPerPage()
);
+
+ return new LengthAwarePaginator($this->get($columns), $total, $perPage, $page, [
+ 'path' => Paginator::resolveCurrentPath(),
+ 'pageName' => $pageName,
+ ]);
}
/**
- * Find a model by its primary key.
+ * Paginate the given query into a simple paginator.
*
- * @param mixed $id
- * @param array|string $columns
- * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TModel|null)
+ * @param int $perPage
+ * @param array $columns
+ * @param string $pageName
+ * @return \Illuminate\Contracts\Pagination\Paginator
*/
- public function find($id, $columns = ['*'])
+ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page')
{
- if (is_array($id) || $id instanceof Arrayable) {
- return $this->findMany($id, $columns);
- }
+ $page = Paginator::resolveCurrentPage($pageName);
- return $this->whereKey($id)->first($columns);
+ $perPage = $perPage ?: $this->model->getPerPage();
+
+ $this->skip(($page - 1) * $perPage)->take($perPage + 1);
+
+ return new Paginator($this->get($columns), $perPage, $page, [
+ 'path' => Paginator::resolveCurrentPath(),
+ 'pageName' => $pageName,
+ ]);
}
/**
- * Find a sole model by its primary key.
- *
- * @param mixed $id
- * @param array|string $columns
- * @return TModel
+ * Update a record in the database.
*
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- * @throws \Illuminate\Database\MultipleRecordsFoundException
+ * @param array $values
+ * @return int
*/
- public function findSole($id, $columns = ['*'])
+ public function update(array $values)
{
- return $this->whereKey($id)->sole($columns);
+ return $this->toBase()->update($this->addUpdatedAtColumn($values));
}
/**
- * Find multiple models by their primary keys.
+ * Increment a column's value by a given amount.
*
- * @param \Illuminate\Contracts\Support\Arrayable|array $ids
- * @param array|string $columns
- * @return \Illuminate\Database\Eloquent\Collection
+ * @param string $column
+ * @param int $amount
+ * @param array $extra
+ * @return int
*/
- public function findMany($ids, $columns = ['*'])
+ public function increment($column, $amount = 1, array $extra = [])
{
- $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
-
- if (empty($ids)) {
- return $this->model->newCollection();
- }
+ $extra = $this->addUpdatedAtColumn($extra);
- return $this->whereKey($ids)->get($columns);
+ return $this->toBase()->increment($column, $amount, $extra);
}
/**
- * Find a model by its primary key or throw an exception.
- *
- * @param mixed $id
- * @param array|string $columns
- * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TModel)
+ * Decrement a column's value by a given amount.
*
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
+ * @param string $column
+ * @param int $amount
+ * @param array $extra
+ * @return int
*/
- public function findOrFail($id, $columns = ['*'])
+ public function decrement($column, $amount = 1, array $extra = [])
{
- $result = $this->find($id, $columns);
+ $extra = $this->addUpdatedAtColumn($extra);
- $id = $id instanceof Arrayable ? $id->toArray() : $id;
-
- if (is_array($id)) {
- if (count($result) !== count(array_unique($id))) {
- throw (new ModelNotFoundException)->setModel(
- get_class($this->model), array_diff($id, $result->modelKeys())
- );
- }
-
- return $result;
- }
-
- if (is_null($result)) {
- throw (new ModelNotFoundException)->setModel(
- get_class($this->model), $id
- );
- }
-
- return $result;
- }
-
- /**
- * Find a model by its primary key or return fresh model instance.
- *
- * @param mixed $id
- * @param array|string $columns
- * @return ($id is (\Illuminate\Contracts\Support\Arrayable|array) ? \Illuminate\Database\Eloquent\Collection : TModel)
- */
- public function findOrNew($id, $columns = ['*'])
- {
- if (! is_null($model = $this->find($id, $columns))) {
- return $model;
- }
-
- return $this->newModelInstance();
- }
-
- /**
- * Find a model by its primary key or call a callback.
- *
- * @template TValue
- *
- * @param mixed $id
- * @param (\Closure(): TValue)|list|string $columns
- * @param (\Closure(): TValue)|null $callback
- * @return (
- * $id is (\Illuminate\Contracts\Support\Arrayable|array)
- * ? \Illuminate\Database\Eloquent\Collection
- * : TModel|TValue
- * )
- */
- public function findOr($id, $columns = ['*'], ?Closure $callback = null)
- {
- if ($columns instanceof Closure) {
- $callback = $columns;
-
- $columns = ['*'];
- }
-
- if (! is_null($model = $this->find($id, $columns))) {
- return $model;
- }
-
- return $callback();
- }
-
- /**
- * Get the first record matching the attributes or instantiate it.
- *
- * @param array $attributes
- * @param array $values
- * @return TModel
- */
- public function firstOrNew(array $attributes = [], array $values = [])
- {
- if (! is_null($instance = $this->where($attributes)->first())) {
- return $instance;
- }
-
- return $this->newModelInstance(array_merge($attributes, $values));
- }
-
- /**
- * Get the first record matching the attributes. If the record is not found, create it.
- *
- * @param array $attributes
- * @param (\Closure(): array)|array $values
- * @return TModel
- */
- public function firstOrCreate(array $attributes = [], Closure|array $values = [])
- {
- if (! is_null($instance = (clone $this)->where($attributes)->first())) {
- return $instance;
- }
-
- return $this->createOrFirst($attributes, $values);
- }
-
- /**
- * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record.
- *
- * @param array $attributes
- * @param (\Closure(): array)|array $values
- * @return TModel
- *
- * @throws \Illuminate\Database\UniqueConstraintViolationException
- */
- public function createOrFirst(array $attributes = [], Closure|array $values = [])
- {
- try {
- return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, value($values))));
- } catch (UniqueConstraintViolationException $e) {
- return $this->useWritePdo()->where($attributes)->first() ?? throw $e;
- }
- }
+ return $this->toBase()->decrement($column, $amount, $extra);
+ }
/**
- * Create or update a record matching the attributes, and fill it with values.
+ * Add the "updated at" column to an array of values.
*
- * @param array $attributes
* @param array $values
- * @return TModel
- */
- public function updateOrCreate(array $attributes, array $values = [])
- {
- return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) {
- if (! $instance->wasRecentlyCreated) {
- $instance->fill($values)->save();
- }
- });
- }
-
- /**
- * Create a record matching the attributes, or increment the existing record.
- *
- * @param array $attributes
- * @param string $column
- * @param int|float $default
- * @param int|float $step
- * @param array $extra
- * @return TModel
- */
- public function incrementOrCreate(array $attributes, string $column = 'count', $default = 1, $step = 1, array $extra = [])
- {
- return tap($this->firstOrCreate($attributes, [$column => $default]), function ($instance) use ($column, $step, $extra) {
- if (! $instance->wasRecentlyCreated) {
- $instance->increment($column, $step, $extra);
- }
- });
- }
-
- /**
- * Execute the query and get the first result or throw an exception.
- *
- * @param array|string $columns
- * @return TModel
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function firstOrFail($columns = ['*'])
- {
- if (! is_null($model = $this->first($columns))) {
- return $model;
- }
-
- throw (new ModelNotFoundException)->setModel(get_class($this->model));
- }
-
- /**
- * Execute the query and get the first result or call a callback.
- *
- * @template TValue
- *
- * @param (\Closure(): TValue)|list $columns
- * @param (\Closure(): TValue)|null $callback
- * @return TModel|TValue
+ * @return array
*/
- public function firstOr($columns = ['*'], ?Closure $callback = null)
+ protected function addUpdatedAtColumn(array $values)
{
- if ($columns instanceof Closure) {
- $callback = $columns;
-
- $columns = ['*'];
- }
-
- if (! is_null($model = $this->first($columns))) {
- return $model;
+ if (! $this->model->usesTimestamps()) {
+ return $values;
}
- return $callback();
- }
+ $column = $this->model->getUpdatedAtColumn();
- /**
- * Execute the query and get the first result if it's the sole matching record.
- *
- * @param array|string $columns
- * @return TModel
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- * @throws \Illuminate\Database\MultipleRecordsFoundException
- */
- public function sole($columns = ['*'])
- {
- try {
- return $this->baseSole($columns);
- } catch (RecordsNotFoundException) {
- throw (new ModelNotFoundException)->setModel(get_class($this->model));
- }
+ return Arr::add($values, $column, $this->model->freshTimestampString());
}
/**
- * Get a single column's value from the first result of a query.
+ * Delete a record from the database.
*
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @return mixed
*/
- public function value($column)
+ public function delete()
{
- if ($result = $this->first([$column])) {
- $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column;
-
- return $result->{Str::afterLast($column, '.')};
+ if (isset($this->onDelete)) {
+ return call_user_func($this->onDelete, $this);
}
- }
-
- /**
- * Get a single column's value from the first result of a query if it's the sole matching record.
- *
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @return mixed
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- * @throws \Illuminate\Database\MultipleRecordsFoundException
- */
- public function soleValue($column)
- {
- $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column;
- return $this->sole([$column])->{Str::afterLast($column, '.')};
+ return $this->toBase()->delete();
}
/**
- * Get a single column's value from the first result of the query or throw an exception.
+ * Run the default delete function on the builder.
*
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
* @return mixed
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
- public function valueOrFail($column)
+ public function forceDelete()
{
- $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column;
-
- return $this->firstOrFail([$column])->{Str::afterLast($column, '.')};
+ return $this->query->delete();
}
/**
- * Execute the query as a "select" statement.
+ * Register a replacement for the default delete function.
*
- * @param array|string $columns
- * @return \Illuminate\Database\Eloquent\Collection
+ * @param \Closure $callback
+ * @return void
*/
- public function get($columns = ['*'])
+ public function onDelete(Closure $callback)
{
- $builder = $this->applyScopes();
-
- // If we actually found models we will also eager load any relationships that
- // have been specified as needing to be eager loaded, which will solve the
- // n+1 query issue for the developers to avoid running a lot of queries.
- if (count($models = $builder->getModels($columns)) > 0) {
- $models = $builder->eagerLoadRelations($models);
- }
-
- return $this->applyAfterQueryCallbacks(
- $builder->getModel()->newCollection($models)
- );
+ $this->onDelete = $callback;
}
/**
* Get the hydrated models without eager loading.
*
- * @param array|string $columns
- * @return array
+ * @param array $columns
+ * @return \Illuminate\Database\Eloquent\Model[]
*/
public function getModels($columns = ['*'])
{
- return $this->model->hydrate(
- $this->query->get($columns)->all()
- )->all();
+ $results = $this->query->get($columns)->all();
+
+ $connection = $this->model->getConnectionName();
+
+ return $this->model->hydrate($results, $connection)->all();
}
/**
* Eager load the relationships for the models.
*
- * @param array $models
- * @return array
+ * @param array $models
+ * @return array
*/
public function eagerLoadRelations(array $models)
{
@@ -920,8 +610,8 @@ public function eagerLoadRelations(array $models)
// For nested eager loads we'll skip loading them here and they will be set as an
// eager load on the query to retrieve the relation so that they will be eager
// loaded on that query, because that is where they get hydrated as models.
- if (! str_contains($name, '.')) {
- $models = $this->eagerLoadRelation($models, $name, $constraints);
+ if (strpos($name, '.') === false) {
+ $models = $this->loadRelation($models, $name, $constraints);
}
}
@@ -936,7 +626,7 @@ public function eagerLoadRelations(array $models)
* @param \Closure $constraints
* @return array
*/
- protected function eagerLoadRelation(array $models, $name, Closure $constraints)
+ protected function loadRelation(array $models, $name, Closure $constraints)
{
// First we will "back up" the existing where conditions on the query so we can
// add our eager constraints. Then we will merge the wheres that were on the
@@ -945,37 +635,34 @@ protected function eagerLoadRelation(array $models, $name, Closure $constraints)
$relation->addEagerConstraints($models);
- $constraints($relation);
+ call_user_func($constraints, $relation);
+
+ $models = $relation->initRelation($models, $name);
// Once we have the results, we just match those back up to their parent models
// using the relationship instance. Then we just return the finished arrays
// of models which have been eagerly hydrated and are readied for return.
- return $relation->match(
- $relation->initRelation($models, $name),
- $relation->getEager(), $name
- );
+ $results = $relation->getEager();
+
+ return $relation->match($models, $results, $name);
}
/**
* Get the relation instance for the given relation name.
*
* @param string $name
- * @return \Illuminate\Database\Eloquent\Relations\Relation<\Illuminate\Database\Eloquent\Model, TModel, *>
+ * @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function getRelation($name)
{
// We want to run a relationship query without any constrains so that we will
// not have to remove these where clauses manually which gets really hacky
- // and error prone. We don't want constraints because we add eager ones.
+ // and is error prone while we remove the developer's own where clauses.
$relation = Relation::noConstraints(function () use ($name) {
- try {
- return $this->getModel()->newInstance()->$name();
- } catch (BadMethodCallException) {
- throw RelationNotFoundException::make($this->getModel(), $name);
- }
+ return $this->getModel()->$name();
});
- $nested = $this->relationsNestedUnder($name);
+ $nested = $this->nestedRelations($name);
// If there are nested relationships set on the query, we will put those onto
// the query instances so that they can be handled after this relationship
@@ -993,7 +680,7 @@ public function getRelation($name)
* @param string $relation
* @return array
*/
- protected function relationsNestedUnder($relation)
+ protected function nestedRelations($relation)
{
$nested = [];
@@ -1001,7 +688,7 @@ protected function relationsNestedUnder($relation)
// the given top-level relationship. We will just check for any relations
// that start with the given top relations and adds them to our arrays.
foreach ($this->eagerLoad as $name => $constraints) {
- if ($this->isNestedUnder($relation, $name)) {
+ if ($this->isNested($name, $relation)) {
$nested[substr($name, strlen($relation.'.'))] = $constraints;
}
}
@@ -1012,544 +699,382 @@ protected function relationsNestedUnder($relation)
/**
* Determine if the relationship is nested.
*
- * @param string $relation
* @param string $name
+ * @param string $relation
* @return bool
*/
- protected function isNestedUnder($relation, $name)
+ protected function isNested($name, $relation)
{
- return str_contains($name, '.') && str_starts_with($name, $relation.'.');
+ $dots = Str::contains($name, '.');
+
+ return $dots && Str::startsWith($name, $relation.'.');
}
/**
- * Register a closure to be invoked after the query is executed.
+ * Add a basic where clause to the query.
*
- * @param \Closure $callback
+ * @param string $column
+ * @param string $operator
+ * @param mixed $value
+ * @param string $boolean
* @return $this
*/
- public function afterQuery(Closure $callback)
+ public function where($column, $operator = null, $value = null, $boolean = 'and')
{
- $this->afterQueryCallbacks[] = $callback;
+ if ($column instanceof Closure) {
+ $query = $this->model->newQueryWithoutScopes();
- return $this;
- }
+ call_user_func($column, $query);
- /**
- * Invoke the "after query" modification callbacks.
- *
- * @param mixed $result
- * @return mixed
- */
- public function applyAfterQueryCallbacks($result)
- {
- foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
- $result = $afterQueryCallback($result) ?: $result;
+ $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
+ } else {
+ call_user_func_array([$this->query, 'where'], func_get_args());
}
- return $result;
+ return $this;
}
/**
- * Get a lazy collection for the given query.
+ * Add an "or where" clause to the query.
*
- * @return \Illuminate\Support\LazyCollection
+ * @param string $column
+ * @param string $operator
+ * @param mixed $value
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function cursor()
+ public function orWhere($column, $operator = null, $value = null)
{
- return $this->applyScopes()->query->cursor()->map(function ($record) {
- $model = $this->newModelInstance()->newFromBuilder($record);
-
- return $this->applyAfterQueryCallbacks($this->newModelInstance()->newCollection([$model]))->first();
- })->reject(fn ($model) => is_null($model));
+ return $this->where($column, $operator, $value, 'or');
}
/**
- * Add a generic "order by" clause if the query doesn't already have one.
+ * Add a relationship count / exists condition to the query.
*
- * @return void
+ * @param string $relation
+ * @param string $operator
+ * @param int $count
+ * @param string $boolean
+ * @param \Closure|null $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- protected function enforceOrderBy()
+ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
- if (empty($this->query->orders) && empty($this->query->unionOrders)) {
- $this->orderBy($this->model->getQualifiedKeyName(), 'asc');
+ if (strpos($relation, '.') !== false) {
+ return $this->hasNested($relation, $operator, $count, $boolean, $callback);
}
- }
- /**
- * Get a collection with the values of a given column.
- *
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @param string|null $key
- * @return \Illuminate\Support\Collection
- */
- public function pluck($column, $key = null)
- {
- $results = $this->toBase()->pluck($column, $key);
+ $relation = $this->getHasRelationQuery($relation);
- $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column;
+ // 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
+ // the full "count" clause. This will make the query run much faster.
+ $queryType = $this->shouldRunExistsQuery($operator, $count)
+ ? 'getRelationQuery' : 'getRelationCountQuery';
- $column = Str::after($column, "{$this->model->getTable()}.");
+ $query = $relation->{$queryType}($relation->getRelated()->newQuery(), $this);
- // If the model has a mutator for the requested column, we will spin through
- // the results and mutate the values so that the mutated version of these
- // columns are returned as you would expect from these Eloquent models.
- if (! $this->model->hasAnyGetMutator($column) &&
- ! $this->model->hasCast($column) &&
- ! in_array($column, $this->model->getDates())) {
- return $this->applyAfterQueryCallbacks($results);
+ if ($callback) {
+ call_user_func($callback, $query);
}
- return $this->applyAfterQueryCallbacks(
- $results->map(function ($value) use ($column) {
- return $this->model->newFromBuilder([$column => $value])->{$column};
- })
+ return $this->addHasWhere(
+ $query, $relation, $operator, $count, $boolean
);
}
/**
- * Paginate the given query.
- *
- * @param int|null|\Closure $perPage
- * @param array|string $columns
- * @param string $pageName
- * @param int|null $page
- * @param \Closure|int|null $total
- * @return \Illuminate\Pagination\LengthAwarePaginator
+ * Add nested relationship count / exists conditions to the query.
*
- * @throws \InvalidArgumentException
+ * @param string $relations
+ * @param string $operator
+ * @param int $count
+ * @param string $boolean
+ * @param \Closure|null $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null, $total = null)
+ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
{
- $page = $page ?: Paginator::resolveCurrentPage($pageName);
-
- $total = value($total) ?? $this->toBase()->getCountForPagination();
-
- $perPage = value($perPage, $total) ?: $this->model->getPerPage();
+ $relations = explode('.', $relations);
- $results = $total
- ? $this->forPage($page, $perPage)->get($columns)
- : $this->model->newCollection();
+ // 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.
+ $closure = function ($q) use (&$closure, &$relations, $operator, $count, $boolean, $callback) {
+ if (count($relations) > 1) {
+ $q->whereHas(array_shift($relations), $closure);
+ } else {
+ $q->has(array_shift($relations), $operator, $count, 'and', $callback);
+ }
+ };
- return $this->paginator($results, $total, $perPage, $page, [
- 'path' => Paginator::resolveCurrentPath(),
- 'pageName' => $pageName,
- ]);
+ return $this->has(array_shift($relations), '>=', 1, $boolean, $closure);
}
/**
- * Paginate the given query into a simple paginator.
+ * Add a relationship count / exists condition to the query.
*
- * @param int|null $perPage
- * @param array|string $columns
- * @param string $pageName
- * @param int|null $page
- * @return \Illuminate\Contracts\Pagination\Paginator
+ * @param string $relation
+ * @param string $boolean
+ * @param \Closure|null $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
+ public function doesntHave($relation, $boolean = 'and', Closure $callback = null)
{
- $page = $page ?: Paginator::resolveCurrentPage($pageName);
-
- $perPage = $perPage ?: $this->model->getPerPage();
-
- // Next we will set the limit and offset for this query so that when we get the
- // results we get the proper section of results. Then, we'll create the full
- // paginator instances for these results with the given page and per page.
- $this->offset(($page - 1) * $perPage)->limit($perPage + 1);
-
- return $this->simplePaginator($this->get($columns), $perPage, $page, [
- 'path' => Paginator::resolveCurrentPath(),
- 'pageName' => $pageName,
- ]);
+ return $this->has($relation, '<', 1, $boolean, $callback);
}
/**
- * Paginate the given query into a cursor paginator.
+ * Add a relationship count / exists condition to the query with where clauses.
*
- * @param int|null $perPage
- * @param array|string $columns
- * @param string $cursorName
- * @param \Illuminate\Pagination\Cursor|string|null $cursor
- * @return \Illuminate\Contracts\Pagination\CursorPaginator
+ * @param string $relation
+ * @param \Closure $callback
+ * @param string $operator
+ * @param int $count
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
+ public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
{
- $perPage = $perPage ?: $this->model->getPerPage();
-
- return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor);
+ return $this->has($relation, $operator, $count, 'and', $callback);
}
/**
- * Ensure the proper order by required for cursor pagination.
+ * Add a relationship count / exists condition to the query with where clauses.
*
- * @param bool $shouldReverse
- * @return \Illuminate\Support\Collection
+ * @param string $relation
+ * @param \Closure|null $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- protected function ensureOrderForCursorPagination($shouldReverse = false)
+ public function whereDoesntHave($relation, Closure $callback = null)
{
- if (empty($this->query->orders) && empty($this->query->unionOrders)) {
- $this->enforceOrderBy();
- }
-
- $reverseDirection = function ($order) {
- if (! isset($order['direction'])) {
- return $order;
- }
-
- $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
-
- return $order;
- };
-
- if ($shouldReverse) {
- $this->query->orders = (new BaseCollection($this->query->orders))->map($reverseDirection)->toArray();
- $this->query->unionOrders = (new BaseCollection($this->query->unionOrders))->map($reverseDirection)->toArray();
- }
-
- $orders = ! empty($this->query->unionOrders) ? $this->query->unionOrders : $this->query->orders;
-
- return (new BaseCollection($orders))
- ->filter(fn ($order) => Arr::has($order, 'direction'))
- ->values();
+ return $this->doesntHave($relation, 'and', $callback);
}
/**
- * Save a new model and return the instance.
+ * Add a relationship count / exists condition to the query with an "or".
*
- * @param array $attributes
- * @return TModel
+ * @param string $relation
+ * @param string $operator
+ * @param int $count
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function create(array $attributes = [])
+ public function orHas($relation, $operator = '>=', $count = 1)
{
- return tap($this->newModelInstance($attributes), function ($instance) {
- $instance->save();
- });
+ return $this->has($relation, $operator, $count, 'or');
}
/**
- * Save a new model and return the instance without raising model events.
+ * Add a relationship count / exists condition to the query with where clauses and an "or".
*
- * @param array $attributes
- * @return TModel
+ * @param string $relation
+ * @param \Closure $callback
+ * @param string $operator
+ * @param int $count
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function createQuietly(array $attributes = [])
+ public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
{
- return Model::withoutEvents(fn () => $this->create($attributes));
+ return $this->has($relation, $operator, $count, 'or', $callback);
}
/**
- * Save a new model and return the instance. Allow mass-assignment.
+ * Add the "has" condition where clause to the query.
*
- * @param array $attributes
- * @return TModel
+ * @param \Illuminate\Database\Eloquent\Builder $hasQuery
+ * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
+ * @param string $operator
+ * @param int $count
+ * @param string $boolean
+ * @return \Illuminate\Database\Eloquent\Builder
*/
- public function forceCreate(array $attributes)
+ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean)
{
- return $this->model->unguarded(function () use ($attributes) {
- return $this->newModelInstance()->create($attributes);
- });
- }
+ $this->mergeModelDefinedRelationWheresToHasQuery($hasQuery, $relation);
- /**
- * Save a new model instance with mass assignment without raising model events.
- *
- * @param array $attributes
- * @return TModel
- */
- public function forceCreateQuietly(array $attributes = [])
- {
- return Model::withoutEvents(fn () => $this->forceCreate($attributes));
+ if ($this->shouldRunExistsQuery($operator, $count)) {
+ $not = ($operator === '<' && $count === 1);
+
+ return $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $not);
+ }
+
+ return $this->whereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
}
/**
- * Update records in the database.
+ * Check if we can run an "exists" query to optimize performance.
*
- * @param array $values
- * @return int
+ * @param string $operator
+ * @param int $count
+ * @return bool
*/
- public function update(array $values)
+ protected function shouldRunExistsQuery($operator, $count)
{
- return $this->toBase()->update($this->addUpdatedAtColumn($values));
+ return ($operator === '>=' || $operator === '<') && $count === 1;
}
/**
- * Insert new records or update the existing ones.
+ * Add a sub query count clause to the query.
*
- * @param array $values
- * @param array|string $uniqueBy
- * @param array|null $update
- * @return int
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param string $operator
+ * @param int $count
+ * @param string $boolean
+ * @return $this
*/
- public function upsert(array $values, $uniqueBy, $update = null)
+ protected function whereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
{
- if (empty($values)) {
- return 0;
+ if (is_numeric($count)) {
+ $count = new Expression($count);
}
- if (! is_array(array_first($values))) {
- $values = [$values];
- }
-
- if (is_null($update)) {
- $update = array_keys(array_first($values));
- }
+ $this->query->addBinding($query->getBindings(), 'where');
- return $this->toBase()->upsert(
- $this->addTimestampsToUpsertValues($this->addUniqueIdsToUpsertValues($values)),
- $uniqueBy,
- $this->addUpdatedAtToUpsertColumns($update)
- );
+ return $this->where(new Expression('('.$query->toSql().')'), $operator, $count, $boolean);
}
/**
- * Update the column's update timestamp.
+ * Merge the "wheres" from a relation query to a has query.
*
- * @param string|null $column
- * @return int|false
+ * @param \Illuminate\Database\Eloquent\Builder $hasQuery
+ * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
+ * @return void
*/
- public function touch($column = null)
+ protected function mergeModelDefinedRelationWheresToHasQuery(Builder $hasQuery, Relation $relation)
{
- $time = $this->model->freshTimestamp();
-
- if ($column) {
- return $this->toBase()->update([$column => $time]);
- }
-
- $column = $this->model->getUpdatedAtColumn();
-
- if (! $this->model->usesTimestamps() || is_null($column)) {
- return false;
- }
+ $removedScopes = $hasQuery->removedScopes();
- return $this->toBase()->update([$column => $time]);
- }
+ $relationQuery = $relation->withoutGlobalScopes($removedScopes)->toBase();
- /**
- * Increment a column's value by a given amount.
- *
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @param float|int $amount
- * @param array $extra
- * @return int
- */
- public function increment($column, $amount = 1, array $extra = [])
- {
- return $this->toBase()->increment(
- $column, $amount, $this->addUpdatedAtColumn($extra)
+ // Here we have the "has" query and the original relation. We need to copy over any
+ // where clauses the developer may have put in the relationship function over to
+ // the has query, and then copy the bindings from the "has" query to the main.
+ $hasQuery->withoutGlobalScopes()->mergeWheres(
+ $relationQuery->wheres, $relationQuery->getBindings()
);
}
/**
- * Decrement a column's value by a given amount.
+ * Get the "has relation" base query instance.
*
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @param float|int $amount
- * @param array $extra
- * @return int
+ * @param string $relation
+ * @return \Illuminate\Database\Eloquent\Relations\Relation
*/
- public function decrement($column, $amount = 1, array $extra = [])
+ protected function getHasRelationQuery($relation)
{
- return $this->toBase()->decrement(
- $column, $amount, $this->addUpdatedAtColumn($extra)
- );
+ return Relation::noConstraints(function () use ($relation) {
+ return $this->getModel()->$relation();
+ });
}
/**
- * Add the "updated at" column to an array of values.
+ * Set the relationships that should be eager loaded.
*
- * @param array $values
- * @return array
+ * @param mixed $relations
+ * @return $this
*/
- protected function addUpdatedAtColumn(array $values)
+ public function with($relations)
{
- if (! $this->model->usesTimestamps() ||
- is_null($this->model->getUpdatedAtColumn())) {
- return $values;
- }
-
- $column = $this->model->getUpdatedAtColumn();
-
- if (! array_key_exists($column, $values)) {
- $timestamp = $this->model->freshTimestampString();
-
- if (
- $this->model->hasSetMutator($column)
- || $this->model->hasAttributeSetMutator($column)
- || $this->model->hasCast($column)
- ) {
- $timestamp = $this->model->newInstance()
- ->forceFill([$column => $timestamp])
- ->getAttributes()[$column] ?? $timestamp;
- }
-
- $values = array_merge([$column => $timestamp], $values);
+ if (is_string($relations)) {
+ $relations = func_get_args();
}
- $segments = preg_split('/\s+as\s+/i', $this->query->from);
+ $eagers = $this->parseWithRelations($relations);
- $qualifiedColumn = array_last($segments).'.'.$column;
+ $this->eagerLoad = array_merge($this->eagerLoad, $eagers);
- $values[$qualifiedColumn] = Arr::get($values, $qualifiedColumn, $values[$column]);
-
- unset($values[$column]);
-
- return $values;
+ return $this;
}
/**
- * Add unique IDs to the inserted values.
+ * Parse a list of relations into individuals.
*
- * @param array $values
+ * @param array $relations
* @return array
*/
- protected function addUniqueIdsToUpsertValues(array $values)
+ protected function parseWithRelations(array $relations)
{
- if (! $this->model->usesUniqueIds()) {
- return $values;
- }
-
- foreach ($this->model->uniqueIds() as $uniqueIdAttribute) {
- foreach ($values as &$row) {
- if (! array_key_exists($uniqueIdAttribute, $row)) {
- $row = array_merge([$uniqueIdAttribute => $this->model->newUniqueId()], $row);
- }
- }
- }
-
- return $values;
- }
+ $results = [];
- /**
- * Add timestamps to the inserted values.
- *
- * @param array $values
- * @return array
- */
- protected function addTimestampsToUpsertValues(array $values)
- {
- if (! $this->model->usesTimestamps()) {
- return $values;
- }
+ foreach ($relations as $name => $constraints) {
+ // If the "relation" value is actually a numeric key, we can assume that no
+ // constraints have been specified for the eager load and we'll just put
+ // an empty Closure with the loader so that we can treat all the same.
+ if (is_numeric($name)) {
+ $f = function () {
+ //
+ };
- $timestamp = $this->model->freshTimestampString();
+ list($name, $constraints) = [$constraints, $f];
+ }
- $columns = array_filter([
- $this->model->getCreatedAtColumn(),
- $this->model->getUpdatedAtColumn(),
- ]);
+ // We need to separate out any nested includes. Which allows the developers
+ // to load deep relationships using "dots" without stating each level of
+ // the relationship with its own key in the array of eager load names.
+ $results = $this->parseNestedWith($name, $results);
- foreach ($columns as $column) {
- foreach ($values as &$row) {
- $row = array_merge([$column => $timestamp], $row);
- }
+ $results[$name] = $constraints;
}
- return $values;
+ return $results;
}
/**
- * Add the "updated at" column to the updated columns.
+ * Parse the nested relationships in a relation.
*
- * @param array $update
+ * @param string $name
+ * @param array $results
* @return array
*/
- protected function addUpdatedAtToUpsertColumns(array $update)
+ protected function parseNestedWith($name, $results)
{
- if (! $this->model->usesTimestamps()) {
- return $update;
- }
-
- $column = $this->model->getUpdatedAtColumn();
-
- if (! is_null($column) &&
- ! array_key_exists($column, $update) &&
- ! in_array($column, $update)) {
- $update[] = $column;
- }
+ $progress = [];
- return $update;
- }
+ // If the relation has already been set on the result array, we will not set it
+ // again, since that would override any constraints that were already placed
+ // on the relationships. We will only set the ones that are not specified.
+ foreach (explode('.', $name) as $segment) {
+ $progress[] = $segment;
- /**
- * Delete records from the database.
- *
- * @return mixed
- */
- public function delete()
- {
- if (isset($this->onDelete)) {
- return call_user_func($this->onDelete, $this);
+ if (! isset($results[$last = implode('.', $progress)])) {
+ $results[$last] = function () {
+ //
+ };
+ }
}
- return $this->toBase()->delete();
- }
-
- /**
- * Run the default delete function on the builder.
- *
- * Since we do not apply scopes here, the row will actually be deleted.
- *
- * @return mixed
- */
- public function forceDelete()
- {
- return $this->query->delete();
- }
-
- /**
- * Register a replacement for the default delete function.
- *
- * @param \Closure $callback
- * @return void
- */
- public function onDelete(Closure $callback)
- {
- $this->onDelete = $callback;
+ return $results;
}
/**
- * Determine if the given model has a scope.
+ * Call the given model scope on the underlying model.
*
* @param string $scope
- * @return bool
+ * @param array $parameters
+ * @return \Illuminate\Database\Query\Builder
*/
- public function hasNamedScope($scope)
+ protected function callScope($scope, $parameters)
{
- return $this->model && $this->model->hasNamedScope($scope);
- }
+ array_unshift($parameters, $this);
- /**
- * Call the given local model scopes.
- *
- * @param array|string $scopes
- * @return static|mixed
- */
- public function scopes($scopes)
- {
- $builder = $this;
+ $query = $this->getQuery();
- foreach (Arr::wrap($scopes) as $scope => $parameters) {
- // If the scope key is an integer, then the scope was passed as the value and
- // the parameter list is empty, so we will format the scope name and these
- // parameters here. Then, we'll be ready to call the scope on the model.
- if (is_int($scope)) {
- [$scope, $parameters] = [$parameters, []];
- }
+ // We will keep track of how many wheres are on the query before running the
+ // scope so that we can properly group the added scope constraints in the
+ // query as their own isolated nested where statement and avoid issues.
+ $originalWhereCount = count($query->wheres);
- // Next we'll pass the scope callback to the callScope method which will take
- // care of grouping the "wheres" properly so the logical order doesn't get
- // messed up when adding scopes. Then we'll return back out the builder.
- $builder = $builder->callNamedScope(
- $scope, Arr::wrap($parameters)
- );
+ $result = call_user_func_array([$this->model, $scope], $parameters) ?: $this;
+
+ if ($this->shouldNestWheresForScope($query, $originalWhereCount)) {
+ $this->nestWheresForScope($query, $originalWhereCount);
}
- return $builder;
+ return $result;
}
/**
* Apply the scopes to the Eloquent builder instance and return it.
*
- * @return static
+ * @return \Illuminate\Database\Eloquent\Builder|static
*/
public function applyScopes()
{
@@ -1559,82 +1084,67 @@ public function applyScopes()
$builder = clone $this;
- foreach ($this->scopes as $identifier => $scope) {
- if (! isset($builder->scopes[$identifier])) {
- continue;
- }
+ $query = $builder->getQuery();
- $builder->callScope(function (self $builder) use ($scope) {
- // If the scope is a Closure we will just go ahead and call the scope with the
- // builder instance. The "callScope" method will properly group the clauses
- // that are added to this query so "where" clauses maintain proper logic.
- if ($scope instanceof Closure) {
- $scope($builder);
- }
+ // We will keep track of how many wheres are on the query before running the
+ // scope so that we can properly group the added scope constraints in the
+ // query as their own isolated nested where statement and avoid issues.
+ $originalWhereCount = count($query->wheres);
- // If the scope is a scope object, we will call the apply method on this scope
- // passing in the builder and the model instance. After we run all of these
- // scopes we will return back the builder instance to the outside caller.
- if ($scope instanceof Scope) {
- $scope->apply($builder, $this->getModel());
- }
- });
+ $whereCounts = [$originalWhereCount];
+
+ foreach ($this->scopes as $scope) {
+ $this->applyScope($scope, $builder);
+
+ // Again, we will keep track of the count each time we add where clauses so that
+ // we will properly isolate each set of scope constraints inside of their own
+ // nested where clause to avoid any conflicts or issues with logical order.
+ $whereCounts[] = count($query->wheres);
+ }
+
+ if ($this->shouldNestWheresForScope($query, $originalWhereCount)) {
+ $this->nestWheresForScope($query, $whereCounts);
}
return $builder;
}
/**
- * Apply the given scope on the current builder instance.
+ * Apply a single scope on the given builder instance.
*
- * @param callable $scope
- * @param array $parameters
- * @return mixed
+ * @param \Illuminate\Database\Eloquent\Scope|\Closure $scope
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
*/
- protected function callScope(callable $scope, array $parameters = [])
+ protected function applyScope($scope, $builder)
{
- array_unshift($parameters, $this);
-
- $query = $this->getQuery();
-
- // We will keep track of how many wheres are on the query before running the
- // scope so that we can properly group the added scope constraints in the
- // query as their own isolated nested where statement and avoid issues.
- $originalWhereCount = is_null($query->wheres)
- ? 0
- : count($query->wheres);
-
- $result = $scope(...$parameters) ?? $this;
-
- if (count((array) $query->wheres) > $originalWhereCount) {
- $this->addNewWheresWithinGroup($query, $originalWhereCount);
+ if ($scope instanceof Closure) {
+ $scope($builder);
+ } elseif ($scope instanceof Scope) {
+ $scope->apply($builder, $this->getModel());
}
-
- return $result;
}
/**
- * Apply the given named scope on the current builder instance.
+ * Determine if the scope added after the given offset should be nested.
*
- * @param string $scope
- * @param array $parameters
- * @return mixed
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param int $originalWhereCount
+ * @return bool
*/
- protected function callNamedScope($scope, array $parameters = [])
+ protected function shouldNestWheresForScope(QueryBuilder $query, $originalWhereCount)
{
- return $this->callScope(function (...$parameters) use ($scope) {
- return $this->model->callNamedScope($scope, $parameters);
- }, $parameters);
+ return count($query->wheres) > $originalWhereCount;
}
/**
* Nest where conditions by slicing them at the given where count.
*
* @param \Illuminate\Database\Query\Builder $query
- * @param int $originalWhereCount
+ * @param int|array $whereCounts
* @return void
*/
- protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCount)
+ protected function nestWheresForScope(QueryBuilder $query, $whereCounts)
{
// Here, we totally remove all of the where clauses since we are going to
// rebuild them as nested queries by slicing the groups of wheres into
@@ -1643,33 +1153,43 @@ protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCo
$query->wheres = [];
- $this->groupWhereSliceForScope(
- $query, array_slice($allWheres, 0, $originalWhereCount)
- );
+ // We will construct where offsets by adding the outer most offsets to the
+ // collection (0 and total where count) while also flattening the array
+ // and extracting unique values, ensuring that all wheres are sliced.
+ $whereOffsets = collect([0, $whereCounts, count($allWheres)])
+ ->flatten()->unique();
- $this->groupWhereSliceForScope(
- $query, array_slice($allWheres, $originalWhereCount)
- );
+ $sliceFrom = $whereOffsets->shift();
+
+ foreach ($whereOffsets as $sliceTo) {
+ $this->sliceWhereConditions(
+ $query, $allWheres, $sliceFrom, $sliceTo
+ );
+
+ $sliceFrom = $sliceTo;
+ }
}
/**
- * Slice where conditions at the given offset and add them to the query as a nested condition.
+ * Create a slice of where conditions at the given offsets and nest them if needed.
*
* @param \Illuminate\Database\Query\Builder $query
- * @param array $whereSlice
+ * @param array $wheres
+ * @param int $sliceFrom
+ * @param int $sliceTo
* @return void
*/
- protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice)
+ protected function sliceWhereConditions(QueryBuilder $query, array $wheres, $sliceFrom, $sliceTo)
{
- $whereBooleans = (new BaseCollection($whereSlice))->pluck('boolean');
+ $whereSlice = array_slice($wheres, $sliceFrom, $sliceTo - $sliceFrom);
+
+ $whereBooleans = collect($whereSlice)->pluck('boolean');
// Here we'll check if the given subset of where clauses contains any "or"
// booleans and in this case create a nested where expression. That way
// we don't add any unnecessary nesting thus keeping the query clean.
- if ($whereBooleans->contains(fn ($logicalOperator) => str_contains($logicalOperator, 'or'))) {
- $query->wheres[] = $this->createNestedWhere(
- $whereSlice, str_replace(' not', '', $whereBooleans->first())
- );
+ if ($whereBooleans->contains('or')) {
+ $query->wheres[] = $this->nestWhereSlice($whereSlice, $whereBooleans->first());
} else {
$query->wheres = array_merge($query->wheres, $whereSlice);
}
@@ -1682,7 +1202,7 @@ protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice)
* @param string $boolean
* @return array
*/
- protected function createNestedWhere($whereSlice, $boolean = 'and')
+ protected function nestWhereSlice($whereSlice, $boolean = 'and')
{
$whereGroup = $this->getQuery()->forNestedWhere();
@@ -1692,416 +1212,76 @@ protected function createNestedWhere($whereSlice, $boolean = 'and')
}
/**
- * Specify relationships that should be eager loaded.
+ * Get the underlying query builder instance.
*
- * @param array): mixed)|string>|string $relations
- * @param (\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string|null $callback
- * @return $this
+ * @return \Illuminate\Database\Query\Builder|static
*/
- public function with($relations, $callback = null)
+ public function getQuery()
{
- if ($callback instanceof Closure) {
- $eagerLoad = $this->parseWithRelations([$relations => $callback]);
- } else {
- $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);
- }
-
- $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);
-
- return $this;
+ return $this->query;
}
/**
- * Prevent the specified relations from being eager loaded.
+ * Get a base query builder instance.
*
- * @param mixed $relations
- * @return $this
+ * @return \Illuminate\Database\Query\Builder
*/
- public function without($relations)
+ public function toBase()
{
- $this->eagerLoad = array_diff_key($this->eagerLoad, array_flip(
- is_string($relations) ? func_get_args() : $relations
- ));
-
- return $this;
+ return $this->applyScopes()->getQuery();
}
/**
- * Set the relationships that should be eager loaded while removing any previously added eager loading specifications.
+ * Set the underlying query builder instance.
*
- * @param array): mixed)|string>|string $relations
+ * @param \Illuminate\Database\Query\Builder $query
* @return $this
*/
- public function withOnly($relations)
+ public function setQuery($query)
{
- $this->eagerLoad = [];
+ $this->query = $query;
- return $this->with($relations);
+ return $this;
}
/**
- * Create a new instance of the model being queried.
+ * Get the relationships being eagerly loaded.
*
- * @param array $attributes
- * @return TModel
+ * @return array
*/
- public function newModelInstance($attributes = [])
+ public function getEagerLoads()
{
- $attributes = array_merge($this->pendingAttributes, $attributes);
-
- return $this->model->newInstance($attributes)->setConnection(
- $this->query->getConnection()->getName()
- );
+ return $this->eagerLoad;
}
/**
- * Parse a list of relations into individuals.
+ * Set the relationships being eagerly loaded.
*
- * @param array $relations
- * @return array
+ * @param array $eagerLoad
+ * @return $this
*/
- protected function parseWithRelations(array $relations)
+ public function setEagerLoads(array $eagerLoad)
{
- if ($relations === []) {
- return [];
- }
-
- $results = [];
-
- foreach ($this->prepareNestedWithRelationships($relations) as $name => $constraints) {
- // We need to separate out any nested includes, which allows the developers
- // to load deep relationships using "dots" without stating each level of
- // the relationship with its own key in the array of eager-load names.
- $results = $this->addNestedWiths($name, $results);
-
- $results[$name] = $constraints;
- }
+ $this->eagerLoad = $eagerLoad;
- return $results;
+ return $this;
}
/**
- * Prepare nested with relationships.
+ * Get the model instance being queried.
*
- * @param array $relations
- * @param string $prefix
- * @return array
+ * @return \Illuminate\Database\Eloquent\Model
*/
- protected function prepareNestedWithRelationships($relations, $prefix = '')
+ public function getModel()
{
- $preparedRelationships = [];
-
- if ($prefix !== '') {
- $prefix .= '.';
- }
-
- // If any of the relationships are formatted with the [$attribute => array()]
- // syntax, we shall loop over the nested relations and prepend each key of
- // this array while flattening into the traditional dot notation format.
- foreach ($relations as $key => $value) {
- if (! is_string($key) || ! is_array($value)) {
- continue;
- }
-
- [$attribute, $attributeSelectConstraint] = $this->parseNameAndAttributeSelectionConstraint($key);
-
- $preparedRelationships = array_merge(
- $preparedRelationships,
- ["{$prefix}{$attribute}" => $attributeSelectConstraint],
- $this->prepareNestedWithRelationships($value, "{$prefix}{$attribute}"),
- );
-
- unset($relations[$key]);
- }
-
- // We now know that the remaining relationships are in a dot notation format
- // and may be a string or Closure. We'll loop over them and ensure all of
- // the present Closures are merged + strings are made into constraints.
- foreach ($relations as $key => $value) {
- if (is_numeric($key) && is_string($value)) {
- [$key, $value] = $this->parseNameAndAttributeSelectionConstraint($value);
- }
-
- $preparedRelationships[$prefix.$key] = $this->combineConstraints([
- $value,
- $preparedRelationships[$prefix.$key] ?? static function () {
- //
- },
- ]);
- }
-
- return $preparedRelationships;
- }
-
- /**
- * Combine an array of constraints into a single constraint.
- *
- * @param array $constraints
- * @return \Closure
- */
- protected function combineConstraints(array $constraints)
- {
- return function ($builder) use ($constraints) {
- foreach ($constraints as $constraint) {
- $builder = $constraint($builder) ?? $builder;
- }
-
- return $builder;
- };
- }
-
- /**
- * Parse the attribute select constraints from the name.
- *
- * @param string $name
- * @return array
- */
- protected function parseNameAndAttributeSelectionConstraint($name)
- {
- return str_contains($name, ':')
- ? $this->createSelectWithConstraint($name)
- : [$name, static function () {
- //
- }];
- }
-
- /**
- * Create a constraint to select the given columns for the relation.
- *
- * @param string $name
- * @return array
- */
- protected function createSelectWithConstraint($name)
- {
- return [explode(':', $name)[0], static function ($query) use ($name) {
- $query->select(array_map(static function ($column) use ($query) {
- return $query instanceof BelongsToMany
- ? $query->getRelated()->qualifyColumn($column)
- : $column;
- }, explode(',', explode(':', $name)[1])));
- }];
- }
-
- /**
- * Parse the nested relationships in a relation.
- *
- * @param string $name
- * @param array $results
- * @return array
- */
- protected function addNestedWiths($name, $results)
- {
- $progress = [];
-
- // If the relation has already been set on the result array, we will not set it
- // again, since that would override any constraints that were already placed
- // on the relationships. We will only set the ones that are not specified.
- foreach (explode('.', $name) as $segment) {
- $progress[] = $segment;
-
- if (! isset($results[$last = implode('.', $progress)])) {
- $results[$last] = static function () {
- //
- };
- }
- }
-
- return $results;
- }
-
- /**
- * Specify attributes that should be added to any new models created by this builder.
- *
- * The given key / value pairs will also be added as where conditions to the query.
- *
- * @param \Illuminate\Contracts\Database\Query\Expression|array|string $attributes
- * @param mixed $value
- * @param bool $asConditions
- * @return $this
- */
- public function withAttributes(Expression|array|string $attributes, $value = null, $asConditions = true)
- {
- if (! is_array($attributes)) {
- $attributes = [$attributes => $value];
- }
-
- if ($asConditions) {
- foreach ($attributes as $column => $value) {
- $this->where($this->qualifyColumn($column), $value);
- }
- }
-
- $this->pendingAttributes = array_merge($this->pendingAttributes, $attributes);
-
- return $this;
- }
-
- /**
- * Apply query-time casts to the model instance.
- *
- * @param array $casts
- * @return $this
- */
- public function withCasts($casts)
- {
- $this->model->mergeCasts($casts);
-
- return $this;
- }
-
- /**
- * Execute the given Closure within a transaction savepoint if needed.
- *
- * @template TModelValue
- *
- * @param \Closure(): TModelValue $scope
- * @return TModelValue
- */
- public function withSavepointIfNeeded(Closure $scope): mixed
- {
- return $this->getQuery()->getConnection()->transactionLevel() > 0
- ? $this->getQuery()->getConnection()->transaction($scope)
- : $scope();
- }
-
- /**
- * Get the Eloquent builder instances that are used in the union of the query.
- *
- * @return \Illuminate\Support\Collection
- */
- protected function getUnionBuilders()
- {
- return isset($this->query->unions)
- ? (new BaseCollection($this->query->unions))->pluck('query')
- : new BaseCollection;
- }
-
- /**
- * Get the underlying query builder instance.
- *
- * @return \Illuminate\Database\Query\Builder
- */
- public function getQuery()
- {
- return $this->query;
- }
-
- /**
- * Set the underlying query builder instance.
- *
- * @param \Illuminate\Database\Query\Builder $query
- * @return $this
- */
- public function setQuery($query)
- {
- $this->query = $query;
-
- return $this;
- }
-
- /**
- * Get a base query builder instance.
- *
- * @return \Illuminate\Database\Query\Builder
- */
- public function toBase()
- {
- return $this->applyScopes()->getQuery();
- }
-
- /**
- * Get the relationships being eagerly loaded.
- *
- * @return array
- */
- public function getEagerLoads()
- {
- return $this->eagerLoad;
- }
-
- /**
- * Set the relationships being eagerly loaded.
- *
- * @param array $eagerLoad
- * @return $this
- */
- public function setEagerLoads(array $eagerLoad)
- {
- $this->eagerLoad = $eagerLoad;
-
- return $this;
- }
-
- /**
- * Indicate that the given relationships should not be eagerly loaded.
- *
- * @param array $relations
- * @return $this
- */
- public function withoutEagerLoad(array $relations)
- {
- $relations = array_diff(array_keys($this->model->getRelations()), $relations);
-
- return $this->with($relations);
- }
-
- /**
- * Flush the relationships being eagerly loaded.
- *
- * @return $this
- */
- public function withoutEagerLoads()
- {
- return $this->setEagerLoads([]);
- }
-
- /**
- * Get the "limit" value from the query or null if it's not set.
- *
- * @return mixed
- */
- public function getLimit()
- {
- return $this->query->getLimit();
- }
-
- /**
- * Get the "offset" value from the query or null if it's not set.
- *
- * @return mixed
- */
- public function getOffset()
- {
- return $this->query->getOffset();
- }
-
- /**
- * Get the default key name of the table.
- *
- * @return string
- */
- protected function defaultKeyName()
- {
- return $this->getModel()->getKeyName();
- }
-
- /**
- * Get the model instance being queried.
- *
- * @return TModel
- */
- public function getModel()
- {
- return $this->model;
- }
+ return $this->model;
+ }
/**
* Set a model instance for the model being queried.
*
- * @template TModelNew of \Illuminate\Database\Eloquent\Model
- *
- * @param TModelNew $model
- * @return static
+ * @param \Illuminate\Database\Eloquent\Model $model
+ * @return $this
*/
public function setModel(Model $model)
{
@@ -2113,27 +1293,15 @@ public function setModel(Model $model)
}
/**
- * Qualify the given column name by the model's table.
- *
- * @param string|\Illuminate\Contracts\Database\Query\Expression $column
- * @return string
- */
- public function qualifyColumn($column)
- {
- $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column;
-
- return $this->model->qualifyColumn($column);
- }
-
- /**
- * Qualify the given columns with the model's table.
+ * Extend the builder with a given callback.
*
- * @param array|\Illuminate\Contracts\Database\Query\Expression $columns
- * @return array
+ * @param string $name
+ * @param \Closure $callback
+ * @return void
*/
- public function qualifyColumns($columns)
+ public function macro($name, Closure $callback)
{
- return $this->model->qualifyColumns($columns);
+ $this->macros[$name] = $callback;
}
/**
@@ -2144,180 +1312,33 @@ public function qualifyColumns($columns)
*/
public function getMacro($name)
{
- return Arr::get($this->localMacros, $name);
- }
-
- /**
- * Checks if a macro is registered.
- *
- * @param string $name
- * @return bool
- */
- public function hasMacro($name)
- {
- return isset($this->localMacros[$name]);
- }
-
- /**
- * Get the given global macro by name.
- *
- * @param string $name
- * @return \Closure
- */
- public static function getGlobalMacro($name)
- {
- return Arr::get(static::$macros, $name);
- }
-
- /**
- * Checks if a global macro is registered.
- *
- * @param string $name
- * @return bool
- */
- public static function hasGlobalMacro($name)
- {
- return isset(static::$macros[$name]);
- }
-
- /**
- * Dynamically access builder proxies.
- *
- * @param string $key
- * @return mixed
- *
- * @throws \Exception
- */
- public function __get($key)
- {
- if (in_array($key, ['orWhere', 'whereNot', 'orWhereNot'])) {
- return new HigherOrderBuilderProxy($this, $key);
- }
-
- if (in_array($key, $this->propertyPassthru)) {
- return $this->toBase()->{$key};
- }
-
- throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance.");
+ return Arr::get($this->macros, $name);
}
/**
* Dynamically handle calls into the query instance.
*
* @param string $method
- * @param array $parameters
+ * @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
- if ($method === 'macro') {
- $this->localMacros[$parameters[0]] = $parameters[1];
-
- return;
- }
-
- if ($this->hasMacro($method)) {
+ if (isset($this->macros[$method])) {
array_unshift($parameters, $this);
- return $this->localMacros[$method](...$parameters);
- }
-
- if (static::hasGlobalMacro($method)) {
- $callable = static::$macros[$method];
-
- if ($callable instanceof Closure) {
- $callable = $callable->bindTo($this, static::class);
- }
-
- return $callable(...$parameters);
+ return call_user_func_array($this->macros[$method], $parameters);
}
- if ($this->hasNamedScope($method)) {
- return $this->callNamedScope($method, $parameters);
+ if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
+ return $this->callScope($scope, $parameters);
}
- if (in_array(strtolower($method), $this->passthru)) {
- return $this->toBase()->{$method}(...$parameters);
+ if (in_array($method, $this->passthru)) {
+ return call_user_func_array([$this->toBase(), $method], $parameters);
}
- $this->forwardCallTo($this->query, $method, $parameters);
-
- return $this;
- }
-
- /**
- * Dynamically handle calls into the query instance.
- *
- * @param string $method
- * @param array $parameters
- * @return mixed
- *
- * @throws \BadMethodCallException
- */
- public static function __callStatic($method, $parameters)
- {
- if ($method === 'macro') {
- static::$macros[$parameters[0]] = $parameters[1];
-
- return;
- }
-
- if ($method === 'mixin') {
- return static::registerMixin($parameters[0], $parameters[1] ?? true);
- }
-
- if (! static::hasGlobalMacro($method)) {
- static::throwBadMethodCallException($method);
- }
-
- $callable = static::$macros[$method];
-
- if ($callable instanceof Closure) {
- $callable = $callable->bindTo(null, static::class);
- }
-
- return $callable(...$parameters);
- }
-
- /**
- * Register the given mixin with the builder.
- *
- * @param string $mixin
- * @param bool $replace
- * @return void
- */
- protected static function registerMixin($mixin, $replace)
- {
- $methods = (new ReflectionClass($mixin))->getMethods(
- ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
- );
-
- foreach ($methods as $method) {
- if ($replace || ! static::hasGlobalMacro($method->name)) {
- static::macro($method->name, $method->invoke($mixin));
- }
- }
- }
-
- /**
- * Clone the Eloquent query builder.
- *
- * @return static
- */
- public function clone()
- {
- return clone $this;
- }
-
- /**
- * Register a closure to be invoked on a clone.
- *
- * @param \Closure $callback
- * @return $this
- */
- public function onClone(Closure $callback)
- {
- $this->onCloneCallbacks[] = $callback;
+ call_user_func_array([$this->query, $method], $parameters);
return $this;
}
@@ -2330,9 +1351,5 @@ public function onClone(Closure $callback)
public function __clone()
{
$this->query = clone $this->query;
-
- foreach ($this->onCloneCallbacks as $onCloneCallback) {
- $onCloneCallback($this);
- }
}
}
diff --git a/Eloquent/Casts/ArrayObject.php b/Eloquent/Casts/ArrayObject.php
deleted file mode 100644
index 563545dacb..0000000000
--- a/Eloquent/Casts/ArrayObject.php
+++ /dev/null
@@ -1,47 +0,0 @@
-
- */
-class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable
-{
- /**
- * Get a collection containing the underlying array.
- *
- * @return \Illuminate\Support\Collection
- */
- public function collect()
- {
- return new Collection($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();
- }
-}
diff --git a/Eloquent/Casts/AsArrayObject.php b/Eloquent/Casts/AsArrayObject.php
deleted file mode 100644
index 5ee80d0bb4..0000000000
--- a/Eloquent/Casts/AsArrayObject.php
+++ /dev/null
@@ -1,42 +0,0 @@
-, iterable>
- */
- 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]);
-
- return is_array($data) ? new ArrayObject($data, ArrayObject::ARRAY_AS_PROPS) : 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();
- }
- };
- }
-}
diff --git a/Eloquent/Casts/AsBinary.php b/Eloquent/Casts/AsBinary.php
deleted file mode 100644
index 4f63154777..0000000000
--- a/Eloquent/Casts/AsBinary.php
+++ /dev/null
@@ -1,75 +0,0 @@
-format = $this->arguments[0]
- ?? throw new InvalidArgumentException('The binary codec format is required.');
-
- if (! in_array($this->format, BinaryCodec::formats(), true)) {
- throw new InvalidArgumentException(sprintf(
- 'Unsupported binary codec format [%s]. Allowed formats are: %s.',
- $this->format,
- implode(', ', BinaryCodec::formats()),
- ));
- }
- }
-
- public function get($model, $key, $value, $attributes)
- {
- return BinaryCodec::decode($attributes[$key] ?? null, $this->format);
- }
-
- public function set($model, $key, $value, $attributes)
- {
- return [$key => BinaryCodec::encode($value, $this->format)];
- }
- };
- }
-
- /**
- * Encode / decode values as binary UUIDs.
- */
- public static function uuid(): string
- {
- return self::class.':uuid';
- }
-
- /**
- * Encode / decode values as binary ULIDs.
- */
- public static function ulid(): string
- {
- return self::class.':ulid';
- }
-
- /**
- * Encode / decode values using the given format.
- */
- public static function of(string $format): string
- {
- return self::class.':'.$format;
- }
-}
diff --git a/Eloquent/Casts/AsCollection.php b/Eloquent/Casts/AsCollection.php
deleted file mode 100644
index c6b0ffe4b7..0000000000
--- a/Eloquent/Casts/AsCollection.php
+++ /dev/null
@@ -1,96 +0,0 @@
-, iterable>
- *
- * @throws \InvalidArgumentException
- */
- public static function castUsing(array $arguments)
- {
- return new class($arguments) implements CastsAttributes
- {
- public function __construct(protected array $arguments)
- {
- $this->arguments = array_pad(array_values($this->arguments), 2, '');
- }
-
- public function get($model, $key, $value, $attributes)
- {
- if (! isset($attributes[$key])) {
- return;
- }
-
- $data = Json::decode($attributes[$key]);
-
- $collectionClass = empty($this->arguments[0]) ? Collection::class : $this->arguments[0];
-
- if (! is_a($collectionClass, Collection::class, true)) {
- throw new InvalidArgumentException('The provided class must extend ['.Collection::class.'].');
- }
-
- if (! is_array($data)) {
- return null;
- }
-
- $instance = new $collectionClass($data);
-
- if (! isset($this->arguments[1]) || ! $this->arguments[1]) {
- return $instance;
- }
-
- if (is_string($this->arguments[1])) {
- $this->arguments[1] = Str::parseCallback($this->arguments[1]);
- }
-
- return is_callable($this->arguments[1])
- ? $instance->map($this->arguments[1])
- : $instance->mapInto($this->arguments[1][0]);
- }
-
- public function set($model, $key, $value, $attributes)
- {
- return [$key => Json::encode($value)];
- }
- };
- }
-
- /**
- * Specify the type of object each item in the collection should be mapped to.
- *
- * @param array{class-string, string}|class-string $map
- * @return string
- */
- public static function of($map)
- {
- return static::using('', $map);
- }
-
- /**
- * Specify the collection type for the cast.
- *
- * @param class-string $class
- * @param array{class-string, string}|class-string|null $map
- * @return string
- */
- public static function using($class, $map = null)
- {
- if (is_array($map) && is_callable($map)) {
- $map = $map[0].'@'.$map[1];
- }
-
- return static::class.':'.implode(',', [$class, $map]);
- }
-}
diff --git a/Eloquent/Casts/AsEncryptedArrayObject.php b/Eloquent/Casts/AsEncryptedArrayObject.php
deleted file mode 100644
index 2122a7b1f9..0000000000
--- a/Eloquent/Casts/AsEncryptedArrayObject.php
+++ /dev/null
@@ -1,45 +0,0 @@
-, iterable>
- */
- 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])), ArrayObject::ARRAY_AS_PROPS);
- }
-
- 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;
- }
- };
- }
-}
diff --git a/Eloquent/Casts/AsEncryptedCollection.php b/Eloquent/Casts/AsEncryptedCollection.php
deleted file mode 100644
index a9078c2b38..0000000000
--- a/Eloquent/Casts/AsEncryptedCollection.php
+++ /dev/null
@@ -1,95 +0,0 @@
-, iterable>
- *
- * @throws \InvalidArgumentException
- */
- public static function castUsing(array $arguments)
- {
- return new class($arguments) implements CastsAttributes
- {
- public function __construct(protected array $arguments)
- {
- $this->arguments = array_pad(array_values($this->arguments), 2, '');
- }
-
- public function get($model, $key, $value, $attributes)
- {
- $collectionClass = empty($this->arguments[0]) ? Collection::class : $this->arguments[0];
-
- if (! is_a($collectionClass, Collection::class, true)) {
- throw new InvalidArgumentException('The provided class must extend ['.Collection::class.'].');
- }
-
- if (! isset($attributes[$key])) {
- return null;
- }
-
- $instance = new $collectionClass(Json::decode(Crypt::decryptString($attributes[$key])));
-
- if (! isset($this->arguments[1]) || ! $this->arguments[1]) {
- return $instance;
- }
-
- if (is_string($this->arguments[1])) {
- $this->arguments[1] = Str::parseCallback($this->arguments[1]);
- }
-
- return is_callable($this->arguments[1])
- ? $instance->map($this->arguments[1])
- : $instance->mapInto($this->arguments[1][0]);
- }
-
- public function set($model, $key, $value, $attributes)
- {
- if (! is_null($value)) {
- return [$key => Crypt::encryptString(Json::encode($value))];
- }
-
- return null;
- }
- };
- }
-
- /**
- * Specify the type of object each item in the collection should be mapped to.
- *
- * @param array{class-string, string}|class-string $map
- * @return string
- */
- public static function of($map)
- {
- return static::using('', $map);
- }
-
- /**
- * Specify the collection for the cast.
- *
- * @param class-string $class
- * @param array{class-string, string}|class-string|null $map
- * @return string
- */
- public static function using($class, $map = null)
- {
- if (is_array($map) && is_callable($map)) {
- $map = $map[0].'@'.$map[1];
- }
-
- return static::class.':'.implode(',', [$class, $map]);
- }
-}
diff --git a/Eloquent/Casts/AsEnumArrayObject.php b/Eloquent/Casts/AsEnumArrayObject.php
deleted file mode 100644
index 061dcbf57e..0000000000
--- a/Eloquent/Casts/AsEnumArrayObject.php
+++ /dev/null
@@ -1,97 +0,0 @@
-} $arguments
- * @return \Illuminate\Contracts\Database\Eloquent\CastsAttributes<\Illuminate\Database\Eloquent\Casts\ArrayObject, iterable>
- */
- public static function castUsing(array $arguments)
- {
- return new class($arguments) implements CastsAttributes
- {
- protected $arguments;
-
- public function __construct(array $arguments)
- {
- $this->arguments = $arguments;
- }
-
- public function get($model, $key, $value, $attributes)
- {
- if (! isset($attributes[$key])) {
- return;
- }
-
- $data = Json::decode($attributes[$key]);
-
- if (! is_array($data)) {
- return;
- }
-
- $enumClass = $this->arguments[0];
-
- return new ArrayObject((new Collection($data))->map(function ($value) use ($enumClass) {
- return is_subclass_of($enumClass, BackedEnum::class)
- ? $enumClass::from($value)
- : constant($enumClass.'::'.$value);
- })->toArray());
- }
-
- public function set($model, $key, $value, $attributes)
- {
- if ($value === null) {
- return [$key => null];
- }
-
- $storable = [];
-
- foreach ($value as $enum) {
- $storable[] = $this->getStorableEnumValue($enum);
- }
-
- return [$key => Json::encode($storable)];
- }
-
- public function serialize($model, string $key, $value, array $attributes)
- {
- return (new Collection($value->getArrayCopy()))
- ->map(fn ($enum) => $this->getStorableEnumValue($enum))
- ->toArray();
- }
-
- protected function getStorableEnumValue($enum)
- {
- if (is_string($enum) || is_int($enum)) {
- return $enum;
- }
-
- return enum_value($enum);
- }
- };
- }
-
- /**
- * Specify the Enum for the cast.
- *
- * @param class-string $class
- * @return string
- */
- public static function of($class)
- {
- return static::class.':'.$class;
- }
-}
diff --git a/Eloquent/Casts/AsEnumCollection.php b/Eloquent/Casts/AsEnumCollection.php
deleted file mode 100644
index fa7116a0d0..0000000000
--- a/Eloquent/Casts/AsEnumCollection.php
+++ /dev/null
@@ -1,93 +0,0 @@
-} $arguments
- * @return \Illuminate\Contracts\Database\Eloquent\CastsAttributes<\Illuminate\Support\Collection, iterable>
- */
- public static function castUsing(array $arguments)
- {
- return new class($arguments) implements CastsAttributes
- {
- protected $arguments;
-
- public function __construct(array $arguments)
- {
- $this->arguments = $arguments;
- }
-
- public function get($model, $key, $value, $attributes)
- {
- if (! isset($attributes[$key])) {
- return;
- }
-
- $data = Json::decode($attributes[$key]);
-
- if (! is_array($data)) {
- return;
- }
-
- $enumClass = $this->arguments[0];
-
- return (new Collection($data))->map(function ($value) use ($enumClass) {
- return is_subclass_of($enumClass, BackedEnum::class)
- ? $enumClass::from($value)
- : constant($enumClass.'::'.$value);
- });
- }
-
- public function set($model, $key, $value, $attributes)
- {
- $value = $value !== null
- ? Json::encode((new Collection($value))->map(function ($enum) {
- return $this->getStorableEnumValue($enum);
- })->jsonSerialize())
- : null;
-
- return [$key => $value];
- }
-
- public function serialize($model, string $key, $value, array $attributes)
- {
- return (new Collection($value))
- ->map(fn ($enum) => $this->getStorableEnumValue($enum))
- ->toArray();
- }
-
- protected function getStorableEnumValue($enum)
- {
- if (is_string($enum) || is_int($enum)) {
- return $enum;
- }
-
- return enum_value($enum);
- }
- };
- }
-
- /**
- * Specify the Enum for the cast.
- *
- * @param class-string $class
- * @return string
- */
- public static function of($class)
- {
- return static::class.':'.$class;
- }
-}
diff --git a/Eloquent/Casts/AsFluent.php b/Eloquent/Casts/AsFluent.php
deleted file mode 100644
index bba1b1dac9..0000000000
--- a/Eloquent/Casts/AsFluent.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
- */
- public static function castUsing(array $arguments)
- {
- return new class implements CastsAttributes
- {
- public function get($model, $key, $value, $attributes)
- {
- return isset($value) ? new Fluent(Json::decode($value)) : null;
- }
-
- public function set($model, $key, $value, $attributes)
- {
- return isset($value) ? [$key => Json::encode($value)] : null;
- }
- };
- }
-}
diff --git a/Eloquent/Casts/AsHtmlString.php b/Eloquent/Casts/AsHtmlString.php
deleted file mode 100644
index d4182d258f..0000000000
--- a/Eloquent/Casts/AsHtmlString.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
- */
- public static function castUsing(array $arguments)
- {
- return new class implements CastsAttributes
- {
- public function get($model, $key, $value, $attributes)
- {
- return isset($value) ? new HtmlString($value) : null;
- }
-
- public function set($model, $key, $value, $attributes)
- {
- return isset($value) ? (string) $value : null;
- }
- };
- }
-}
diff --git a/Eloquent/Casts/AsStringable.php b/Eloquent/Casts/AsStringable.php
deleted file mode 100644
index 4f6c787c85..0000000000
--- a/Eloquent/Casts/AsStringable.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
- */
- public static function castUsing(array $arguments)
- {
- return new class implements CastsAttributes
- {
- public function get($model, $key, $value, $attributes)
- {
- return isset($value) ? new Stringable($value) : null;
- }
-
- public function set($model, $key, $value, $attributes)
- {
- return isset($value) ? (string) $value : null;
- }
- };
- }
-}
diff --git a/Eloquent/Casts/AsUri.php b/Eloquent/Casts/AsUri.php
deleted file mode 100644
index d55c6d7996..0000000000
--- a/Eloquent/Casts/AsUri.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
- */
- public static function castUsing(array $arguments)
- {
- return new class implements CastsAttributes
- {
- public function get($model, $key, $value, $attributes)
- {
- return isset($value) ? new Uri($value) : null;
- }
-
- public function set($model, $key, $value, $attributes)
- {
- return isset($value) ? (string) $value : null;
- }
- };
- }
-}
diff --git a/Eloquent/Casts/Attribute.php b/Eloquent/Casts/Attribute.php
deleted file mode 100644
index 26d13ba3fb..0000000000
--- a/Eloquent/Casts/Attribute.php
+++ /dev/null
@@ -1,104 +0,0 @@
-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;
- }
-}
diff --git a/Eloquent/Casts/Json.php b/Eloquent/Casts/Json.php
deleted file mode 100644
index 783d5b9986..0000000000
--- a/Eloquent/Casts/Json.php
+++ /dev/null
@@ -1,56 +0,0 @@
-
- */
-class Collection extends BaseCollection implements QueueableCollection
-{
- use InteractsWithDictionary;
+class Collection extends BaseCollection
+{
/**
* Find a model in the collection by key.
*
- * @template TFindDefault
- *
* @param mixed $key
- * @param TFindDefault $default
- * @return ($key is (\Illuminate\Contracts\Support\Arrayable|array) ? static : TModel|TFindDefault)
+ * @param mixed $default
+ * @return \Illuminate\Database\Eloquent\Model
*/
public function find($key, $default = null)
{
@@ -35,66 +20,26 @@ public function find($key, $default = null)
$key = $key->getKey();
}
- if ($key instanceof Arrayable) {
- $key = $key->toArray();
- }
-
- if (is_array($key)) {
- if ($this->isEmpty()) {
- return new static;
- }
+ return Arr::first($this->items, function ($model) use ($key) {
+ return $model->getKey() == $key;
- return $this->whereIn($this->first()->getKeyName(), $key);
- }
-
- return Arr::first($this->items, fn ($model) => $model->getKey() == $key, $default);
- }
-
- /**
- * Find a model in the collection by key or throw an exception.
- *
- * @param mixed $key
- * @return TModel
- *
- * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
- */
- public function findOrFail($key)
- {
- $result = $this->find($key);
-
- if (is_array($key) && count($result) === count(array_unique($key))) {
- return $result;
- } elseif (! is_array($key) && ! is_null($result)) {
- return $result;
- }
-
- $exception = new ModelNotFoundException;
-
- if (! $model = head($this->items)) {
- throw $exception;
- }
-
- $ids = is_array($key) ? array_diff($key, $result->modelKeys()) : $key;
-
- $exception->setModel(get_class($model), $ids);
-
- throw $exception;
+ }, $default);
}
/**
* Load a set of relationships onto the collection.
*
- * @param array): mixed)|string>|string $relations
+ * @param mixed $relations
* @return $this
*/
public function load($relations)
{
- if ($this->isNotEmpty()) {
+ if (count($this->items) > 0) {
if (is_string($relations)) {
$relations = func_get_args();
}
- $query = $this->first()->newQueryWithoutRelationships()->with($relations);
+ $query = $this->first()->newQuery()->with($relations);
$this->items = $query->eagerLoadRelations($this->items);
}
@@ -103,242 +48,14 @@ public function load($relations)
}
/**
- * Load a set of aggregations over relationship's column onto the collection.
- *
- * @param array): mixed)|string>|string $relations
- * @param string $column
- * @param string|null $function
- * @return $this
- */
- public function loadAggregate($relations, $column, $function = null)
- {
- 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): 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): 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): 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): 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): 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): 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): mixed)|string>|string $relations
- * @return $this
- */
- public function loadMissing($relations)
- {
- if (is_string($relations)) {
- $relations = func_get_args();
- }
-
- if ($this->isNotEmpty()) {
- $query = $this->first()->newQueryWithoutRelationships()->with($relations);
-
- foreach ($query->getEagerLoads() as $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][array_last($segments)] = $value;
- }
-
- $this->loadMissingRelation($this, $path);
- }
- }
-
- return $this;
- }
-
- /**
- * Load a relationship path for models of the given type if it is not already eager loaded.
- *
- * @param array> $tuples
- * @return void
- */
- public function loadMissingRelationshipChain(array $tuples)
- {
- [$relation, $class] = array_shift($tuples);
-
- $this->filter(function ($model) use ($relation, $class) {
- return ! is_null($model) &&
- ! $model->relationLoaded($relation) &&
- $model::class === $class;
- })->load($relation);
-
- if (empty($tuples)) {
- return;
- }
-
- $models = $this->pluck($relation)->whereNotNull();
-
- if ($models->first() instanceof BaseCollection) {
- $models = $models->collapse();
- }
-
- (new static($models))->loadMissingRelationshipChain($tuples);
- }
-
- /**
- * 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(fn ($model) => ! is_null($model) && ! $model->relationLoaded($name))->load($relation);
-
- if (empty($path)) {
- return;
- }
-
- $models = $models->pluck($name)->filter();
-
- 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): mixed)|string> $relations
- * @return $this
- */
- public function loadMorph($relation, $relations)
- {
- $this->pluck($relation)
- ->filter()
- ->groupBy(fn ($model) => get_class($model))
- ->each(fn ($models, $className) => static::make($models)->load($relations[$className] ?? []));
-
- return $this;
- }
-
- /**
- * Load a set of relationship counts onto the mixed relationship collection.
+ * Add an item to the collection.
*
- * @param string $relation
- * @param array): mixed)|string> $relations
+ * @param mixed $item
* @return $this
*/
- public function loadMorphCount($relation, $relations)
+ public function add($item)
{
- $this->pluck($relation)
- ->filter()
- ->groupBy(fn ($model) => get_class($model))
- ->each(fn ($models, $className) => static::make($models)->loadCount($relations[$className] ?? []));
+ $this->items[] = $item;
return $this;
}
@@ -346,51 +63,43 @@ public function loadMorphCount($relation, $relations)
/**
* Determine if a key exists in the collection.
*
- * @param (callable(TModel, TKey): bool)|TModel|string|int $key
- * @param mixed $operator
+ * @param mixed $key
* @param mixed $value
* @return bool
*/
- public function contains($key, $operator = null, $value = null)
+ public function contains($key, $value = null)
{
- if (func_num_args() > 1 || $this->useAsCallable($key)) {
- return parent::contains(...func_get_args());
+ if (func_num_args() == 2) {
+ return parent::contains($key, $value);
}
- if ($key instanceof Model) {
- return parent::contains(fn ($model) => $model->is($key));
+ if ($this->useAsCallable($key)) {
+ return parent::contains($key);
}
- return parent::contains(fn ($model) => $model->getKey() == $key);
- }
+ $key = $key instanceof Model ? $key->getKey() : $key;
- /**
- * Determine if a key does not exist in the collection.
- *
- * @param (callable(TModel, TKey): bool)|TModel|string|int $key
- * @param mixed $operator
- * @param mixed $value
- * @return bool
- */
- public function doesntContain($key, $operator = null, $value = null)
- {
- return ! $this->contains(...func_get_args());
+ return parent::contains(function ($model) use ($key) {
+ return $model->getKey() == $key;
+ });
}
/**
* Get the array of primary keys.
*
- * @return array
+ * @return array
*/
public function modelKeys()
{
- return array_map(fn ($model) => $model->getKey(), $this->items);
+ return array_map(function ($model) {
+ return $model->getKey();
+ }, $this->items);
}
/**
* Merge the collection with the given items.
*
- * @param iterable $items
+ * @param \ArrayAccess|array $items
* @return static
*/
public function merge($items)
@@ -398,73 +107,16 @@ public function merge($items)
$dictionary = $this->getDictionary();
foreach ($items as $item) {
- $dictionary[$this->getDictionaryKey($item->getKey())] = $item;
+ $dictionary[$item->getKey()] = $item;
}
return new static(array_values($dictionary));
}
- /**
- * Run a map over each of the items.
- *
- * @template TMapValue
- *
- * @param callable(TModel, TKey): TMapValue $callback
- * @return \Illuminate\Support\Collection|static
- */
- public function map(callable $callback)
- {
- $result = parent::map($callback);
-
- return $result->contains(fn ($item) => ! $item instanceof Model) ? $result->toBase() : $result;
- }
-
- /**
- * Run an associative map over each of the items.
- *
- * The callback should return an associative array with a single key / value pair.
- *
- * @template TMapWithKeysKey of array-key
- * @template TMapWithKeysValue
- *
- * @param callable(TModel, TKey): array $callback
- * @return \Illuminate\Support\Collection|static
- */
- public function mapWithKeys(callable $callback)
- {
- $result = parent::mapWithKeys($callback);
-
- return $result->contains(fn ($item) => ! $item instanceof Model) ? $result->toBase() : $result;
- }
-
- /**
- * Reload a fresh model instance from the database for all the entities.
- *
- * @param array|string $with
- * @return static
- */
- public function fresh($with = [])
- {
- if ($this->isEmpty()) {
- return new static;
- }
-
- $model = $this->first();
-
- $freshModels = $model->newQueryWithoutScopes()
- ->with(is_string($with) ? func_get_args() : $with)
- ->whereIn($model->getKeyName(), $this->modelKeys())
- ->get()
- ->getDictionary();
-
- return $this->filter(fn ($model) => $model->exists && isset($freshModels[$model->getKey()]))
- ->map(fn ($model) => $freshModels[$model->getKey()]);
- }
-
/**
* Diff the collection with the given items.
*
- * @param iterable $items
+ * @param \ArrayAccess|array $items
* @return static
*/
public function diff($items)
@@ -474,7 +126,7 @@ public function diff($items)
$dictionary = $this->getDictionary($items);
foreach ($this->items as $item) {
- if (! isset($dictionary[$this->getDictionaryKey($item->getKey())])) {
+ if (! isset($dictionary[$item->getKey()])) {
$diff->add($item);
}
}
@@ -485,21 +137,17 @@ public function diff($items)
/**
* Intersect the collection with the given items.
*
- * @param iterable $items
+ * @param \ArrayAccess|array $items
* @return static
*/
public function intersect($items)
{
$intersect = new static;
- if (empty($items)) {
- return $intersect;
- }
-
$dictionary = $this->getDictionary($items);
foreach ($this->items as $item) {
- if (isset($dictionary[$this->getDictionaryKey($item->getKey())])) {
+ if (isset($dictionary[$item->getKey()])) {
$intersect->add($item);
}
}
@@ -510,14 +158,13 @@ public function intersect($items)
/**
* Return only unique items from the collection.
*
- * @param (callable(TModel, TKey): mixed)|string|null $key
- * @param bool $strict
+ * @param string|callable|null $key
* @return static
*/
- public function unique($key = null, $strict = false)
+ public function unique($key = null)
{
if (! is_null($key)) {
- return parent::unique($key, $strict);
+ return parent::unique($key);
}
return new static(array_values($this->getDictionary()));
@@ -526,16 +173,12 @@ public function unique($key = null, $strict = false)
/**
* Returns only the models from the collection with the specified keys.
*
- * @param array|null $keys
+ * @param mixed $keys
* @return static
*/
public function only($keys)
{
- if (is_null($keys)) {
- return new static($this->items);
- }
-
- $dictionary = Arr::only($this->getDictionary(), array_map($this->getDictionaryKey(...), (array) $keys));
+ $dictionary = Arr::only($this->getDictionary(), $keys);
return new static(array_values($dictionary));
}
@@ -543,16 +186,12 @@ public function only($keys)
/**
* Returns all models in the collection except the models with specified keys.
*
- * @param array|null $keys
+ * @param mixed $keys
* @return static
*/
public function except($keys)
{
- if (is_null($keys)) {
- return new static($this->items);
- }
-
- $dictionary = Arr::except($this->getDictionary(), array_map($this->getDictionaryKey(...), (array) $keys));
+ $dictionary = Arr::except($this->getDictionary(), $keys);
return new static(array_values($dictionary));
}
@@ -560,106 +199,34 @@ public function except($keys)
/**
* Make the given, typically visible, attributes hidden across the entire collection.
*
- * @param array|string $attributes
+ * @param array|string $attributes
* @return $this
*/
public function makeHidden($attributes)
{
- return $this->each->makeHidden($attributes);
- }
-
- /**
- * Merge the given, typically visible, attributes hidden across the entire collection.
- *
- * @param array|string $attributes
- * @return $this
- */
- public function mergeHidden($attributes)
- {
- return $this->each->mergeHidden($attributes);
- }
-
- /**
- * Set the hidden attributes across the entire collection.
- *
- * @param array $hidden
- * @return $this
- */
- public function setHidden($hidden)
- {
- return $this->each->setHidden($hidden);
+ return $this->each(function ($model) use ($attributes) {
+ $model->addHidden($attributes);
+ });
}
/**
* Make the given, typically hidden, attributes visible across the entire collection.
*
- * @param array|string $attributes
+ * @param array|string $attributes
* @return $this
*/
public function makeVisible($attributes)
{
- return $this->each->makeVisible($attributes);
- }
-
- /**
- * Merge the given, typically hidden, attributes visible across the entire collection.
- *
- * @param array|string $attributes
- * @return $this
- */
- public function mergeVisible($attributes)
- {
- return $this->each->mergeVisible($attributes);
- }
-
- /**
- * Set the visible attributes across the entire collection.
- *
- * @param array $visible
- * @return $this
- */
- public function setVisible($visible)
- {
- return $this->each->setVisible($visible);
- }
-
- /**
- * Append an attribute across the entire collection.
- *
- * @param array|string $attributes
- * @return $this
- */
- public function append($attributes)
- {
- return $this->each->append($attributes);
- }
-
- /**
- * Sets the appends on every element of the collection, overwriting the existing appends for each.
- *
- * @param array $appends
- * @return $this
- */
- public function setAppends(array $appends)
- {
- return $this->each->setAppends($appends);
- }
-
- /**
- * Remove appended properties from every element in the collection.
- *
- * @return $this
- */
- public function withoutAppends()
- {
- return $this->setAppends([]);
+ return $this->each(function ($model) use ($attributes) {
+ $model->makeVisible($attributes);
+ });
}
/**
* Get a dictionary keyed by primary keys.
*
- * @param iterable|null $items
- * @return array
+ * @param \ArrayAccess|array|null $items
+ * @return array
*/
public function getDictionary($items = null)
{
@@ -668,7 +235,7 @@ public function getDictionary($items = null)
$dictionary = [];
foreach ($items as $value) {
- $dictionary[$this->getDictionaryKey($value->getKey())] = $value;
+ $dictionary[$value->getKey()] = $value;
}
return $dictionary;
@@ -679,256 +246,76 @@ public function getDictionary($items = null)
*/
/**
- * {@inheritDoc}
+ * Get an array with the values of a given key.
*
- * @return \Illuminate\Support\Collection
+ * @param string $value
+ * @param string|null $key
+ * @return \Illuminate\Support\Collection
*/
- #[\Override]
- public function countBy($countBy = null)
- {
- return $this->toBase()->countBy($countBy);
- }
-
- /**
- * {@inheritDoc}
- *
- * @return \Illuminate\Support\Collection
- */
- #[\Override]
- public function collapse()
- {
- return $this->toBase()->collapse();
- }
-
- /**
- * {@inheritDoc}
- *
- * @return \Illuminate\Support\Collection
- */
- #[\Override]
- public function flatten($depth = INF)
- {
- return $this->toBase()->flatten($depth);
- }
-
- /**
- * {@inheritDoc}
- *
- * @return \Illuminate\Support\Collection
- */
- #[\Override]
- public function flip()
+ public function pluck($value, $key = null)
{
- return $this->toBase()->flip();
+ return $this->toBase()->pluck($value, $key);
}
/**
- * {@inheritDoc}
+ * Get the keys of the collection items.
*
- * @return \Illuminate\Support\Collection
+ * @return \Illuminate\Support\Collection
*/
- #[\Override]
public function keys()
{
return $this->toBase()->keys();
}
/**
- * {@inheritDoc}
- *
- * @template TPadValue
- *
- * @return \Illuminate\Support\Collection
- */
- #[\Override]
- public function pad($size, $value)
- {
- return $this->toBase()->pad($size, $value);
- }
-
- /**
- * {@inheritDoc}
- *
- * @return \Illuminate\Support\Collection, static>
- */
- #[\Override]
- public function partition($key, $operator = null, $value = null)
- {
- return parent::partition(...func_get_args())->toBase();
- }
-
- /**
- * {@inheritDoc}
+ * Zip the collection together with one or more arrays.
*
- * @return \Illuminate\Support\Collection
+ * @param mixed ...$items
+ * @return \Illuminate\Support\Collection
*/
- #[\Override]
- public function pluck($value, $key = null)
- {
- return $this->toBase()->pluck($value, $key);
- }
-
- /**
- * {@inheritDoc}
- *
- * @template TZipValue
- *
- * @return \Illuminate\Support\Collection>
- */
- #[\Override]
public function zip($items)
{
- return $this->toBase()->zip(...func_get_args());
- }
-
- /**
- * Get the comparison function to detect duplicates.
- *
- * @return callable(TModel, TModel): bool
- */
- protected function duplicateComparator($strict)
- {
- return fn ($a, $b) => $a->is($b);
- }
-
- /**
- * Enable relationship autoloading for all models in this collection.
- *
- * @return $this
- */
- public function withRelationshipAutoloading()
- {
- $callback = fn ($tuples) => $this->loadMissingRelationshipChain($tuples);
-
- foreach ($this as $model) {
- if (! $model->hasRelationAutoloadCallback()) {
- $model->autoloadRelationsUsing($callback, $this);
- }
- }
-
- return $this;
- }
-
- /**
- * Get the type of the entities being queued.
- *
- * @return string|null
- *
- * @throws \LogicException
- */
- public function getQueueableClass()
- {
- if ($this->isEmpty()) {
- return;
- }
-
- $class = $this->getQueueableModelClass($this->first());
-
- $this->each(function ($model) use ($class) {
- if ($this->getQueueableModelClass($model) !== $class) {
- throw new LogicException('Queueing collections with multiple model types is not supported.');
- }
- });
-
- 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);
+ return call_user_func_array([$this->toBase(), 'zip'], func_get_args());
}
/**
- * Get the identifiers for all of the entities.
+ * Collapse the collection of items into a single array.
*
- * @return array
+ * @return \Illuminate\Support\Collection
*/
- public function getQueueableIds()
+ public function collapse()
{
- if ($this->isEmpty()) {
- return [];
- }
-
- return $this->first() instanceof QueueableEntity
- ? $this->map->getQueueableId()->all()
- : $this->modelKeys();
+ return $this->toBase()->collapse();
}
/**
- * Get the relationships of the entities being queued.
+ * Get a flattened array of the items in the collection.
*
- * @return array
+ * @param int $depth
+ * @return \Illuminate\Support\Collection
*/
- public function getQueueableRelations()
+ public function flatten($depth = INF)
{
- 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));
- }
+ return $this->toBase()->flatten($depth);
}
/**
- * Get the connection of the entities being queued.
- *
- * @return string|null
+ * Flip the items in the collection.
*
- * @throws \LogicException
+ * @return \Illuminate\Support\Collection
*/
- public function getQueueableConnection()
+ public function flip()
{
- 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;
+ return $this->toBase()->flip();
}
/**
- * Get the Eloquent query builder from the collection.
+ * Get a base Support collection instance from this collection.
*
- * @return \Illuminate\Database\Eloquent\Builder
- *
- * @throws \LogicException
+ * @return \Illuminate\Support\Collection
*/
- public function toQuery()
+ public function toBase()
{
- $model = $this->first();
-
- if (! $model) {
- throw new LogicException('Unable to create query for empty collection.');
- }
-
- $class = get_class($model);
-
- if ($this->reject(fn ($model) => $model instanceof $class)->isNotEmpty()) {
- throw new LogicException('Unable to create query for collection with mixed types.');
- }
-
- return $model->newModelQuery()->whereKey($this->modelKeys());
+ return new BaseCollection($this->items);
}
}
diff --git a/Eloquent/Concerns/GuardsAttributes.php b/Eloquent/Concerns/GuardsAttributes.php
deleted file mode 100644
index aa9b2c60c9..0000000000
--- a/Eloquent/Concerns/GuardsAttributes.php
+++ /dev/null
@@ -1,288 +0,0 @@
-
- */
- protected $fillable = [];
-
- /**
- * The attributes that aren't mass assignable.
- *
- * @var array
- */
- protected $guarded = ['*'];
-
- /**
- * Indicates if all mass assignment is enabled.
- *
- * @var bool
- */
- protected static $unguarded = false;
-
- /**
- * The actual columns that exist on the database and can be guarded.
- *
- * @var array>
- */
- protected static $guardableColumns = [];
-
- /**
- * Initialize the GuardsAttributes trait.
- *
- * @return void
- */
- #[Initialize]
- public function initializeGuardsAttributes()
- {
- if (empty($this->fillable)) {
- $this->fillable = static::resolveClassAttribute(Fillable::class, 'columns') ?? [];
- }
-
- if ($this->guarded === ['*']) {
- if (static::resolveClassAttribute(Unguarded::class) !== null) {
- $this->guarded = [];
- } else {
- $this->guarded = static::resolveClassAttribute(Guarded::class, 'columns') ?? ['*'];
- }
- }
- }
-
- /**
- * Get the fillable attributes for the model.
- *
- * @return array
- */
- public function getFillable()
- {
- return $this->fillable;
- }
-
- /**
- * Set the fillable attributes for the model.
- *
- * @param array $fillable
- * @return $this
- */
- public function fillable(array $fillable)
- {
- $this->fillable = $fillable;
-
- return $this;
- }
-
- /**
- * Merge new fillable attributes with existing fillable attributes on the model.
- *
- * @param array $fillable
- * @return $this
- */
- public function mergeFillable(array $fillable)
- {
- $this->fillable = array_values(array_unique(array_merge($this->fillable, $fillable)));
-
- return $this;
- }
-
- /**
- * Get the guarded attributes for the model.
- *
- * @return array
- */
- public function getGuarded()
- {
- return self::$unguarded === true
- ? []
- : $this->guarded;
- }
-
- /**
- * Set the guarded attributes for the model.
- *
- * @param array $guarded
- * @return $this
- */
- public function guard(array $guarded)
- {
- $this->guarded = $guarded;
-
- return $this;
- }
-
- /**
- * Merge new guarded attributes with existing guarded attributes on the model.
- *
- * @param array $guarded
- * @return $this
- */
- public function mergeGuarded(array $guarded)
- {
- $this->guarded = array_values(array_unique(array_merge($this->guarded, $guarded)));
-
- return $this;
- }
-
- /**
- * Disable all mass assignable restrictions.
- *
- * @param bool $state
- * @return void
- */
- public static function unguard($state = true)
- {
- static::$unguarded = $state;
- }
-
- /**
- * Enable the mass assignment restrictions.
- *
- * @return void
- */
- public static function reguard()
- {
- static::$unguarded = false;
- }
-
- /**
- * Determine if the current state is "unguarded".
- *
- * @return bool
- */
- public static function isUnguarded()
- {
- return static::$unguarded;
- }
-
- /**
- * Run the given callable while being unguarded.
- *
- * @template TReturn
- *
- * @param callable(): TReturn $callback
- * @return TReturn
- */
- public static function unguarded(callable $callback)
- {
- if (static::$unguarded) {
- return $callback();
- }
-
- static::unguard();
-
- try {
- return $callback();
- } finally {
- static::reguard();
- }
- }
-
- /**
- * Determine if the given attribute may be mass assigned.
- *
- * @param string $key
- * @return bool
- */
- public function isFillable($key)
- {
- if (static::$unguarded) {
- return true;
- }
-
- // If the key is in the "fillable" array, we can of course assume that it's
- // a fillable attribute. Otherwise, we will check the guarded array when
- // we need to determine if the attribute is black-listed on the model.
- if (in_array($key, $this->getFillable())) {
- return true;
- }
-
- // If the attribute is explicitly listed in the "guarded" array then we can
- // return false immediately. This means this attribute is definitely not
- // fillable and there is no point in going any further in this method.
- if ($this->isGuarded($key)) {
- return false;
- }
-
- return empty($this->getFillable()) &&
- ! str_contains($key, '.') &&
- ! str_starts_with($key, '_');
- }
-
- /**
- * Determine if the given key is guarded.
- *
- * @param string $key
- * @return bool
- */
- public function isGuarded($key)
- {
- if (empty($this->getGuarded())) {
- return false;
- }
-
- return $this->getGuarded() == ['*'] ||
- ! empty(preg_grep('/^'.preg_quote($key, '/').'$/i', $this->getGuarded())) ||
- ! $this->isGuardableColumn($key);
- }
-
- /**
- * Determine if the given column is a valid, guardable column.
- *
- * @param string $key
- * @return bool
- */
- protected function isGuardableColumn($key)
- {
- if ($this->hasSetMutator($key) || $this->hasAttributeSetMutator($key) || $this->isClassCastable($key)) {
- return true;
- }
-
- if (! isset(static::$guardableColumns[get_class($this)])) {
- $columns = $this->getConnection()
- ->getSchemaBuilder()
- ->getColumnListing($this->getTable());
-
- if (empty($columns)) {
- return true;
- }
-
- static::$guardableColumns[get_class($this)] = $columns;
- }
-
- return in_array($key, static::$guardableColumns[get_class($this)]);
- }
-
- /**
- * Determine if the model is totally guarded.
- *
- * @return bool
- */
- public function totallyGuarded()
- {
- return count($this->getFillable()) === 0 && $this->getGuarded() == ['*'];
- }
-
- /**
- * Get the fillable attributes of a given array.
- *
- * @param array $attributes
- * @return array
- */
- protected function fillableFromArray(array $attributes)
- {
- if (count($this->getFillable()) > 0 && ! static::$unguarded) {
- return array_intersect_key($attributes, array_flip($this->getFillable()));
- }
-
- return $attributes;
- }
-}
diff --git a/Eloquent/Concerns/HasAttributes.php b/Eloquent/Concerns/HasAttributes.php
deleted file mode 100644
index a5717d2c30..0000000000
--- a/Eloquent/Concerns/HasAttributes.php
+++ /dev/null
@@ -1,2584 +0,0 @@
-
- */
- protected $attributes = [];
-
- /**
- * The model attribute's original state.
- *
- * @var array
- */
- protected $original = [];
-
- /**
- * The changed model attributes.
- *
- * @var array
- */
- protected $changes = [];
-
- /**
- * The previous state of the changed model attributes.
- *
- * @var array
- */
- protected $previous = [];
-
- /**
- * The attributes that should be cast.
- *
- * @var array
- */
- protected $casts = [];
-
- /**
- * The attributes that have been cast using custom classes.
- *
- * @var array
- */
- protected $classCastCache = [];
-
- /**
- * The attributes that have been cast using "Attribute" return type mutators.
- *
- * @var array
- */
- protected $attributeCastCache = [];
-
- /**
- * The built-in, primitive cast types supported by Eloquent.
- *
- * @var string[]
- */
- protected static $primitiveCastTypes = [
- 'array',
- 'bool',
- 'boolean',
- 'collection',
- 'custom_datetime',
- 'date',
- 'datetime',
- 'decimal',
- 'double',
- 'encrypted',
- 'encrypted:array',
- 'encrypted:collection',
- 'encrypted:json',
- 'encrypted:object',
- 'float',
- 'hashed',
- 'immutable_date',
- 'immutable_datetime',
- 'immutable_custom_datetime',
- 'int',
- 'integer',
- 'json',
- 'json:unicode',
- 'object',
- 'real',
- 'string',
- 'timestamp',
- ];
-
- /**
- * The storage format of the model's date columns.
- *
- * @var string|null
- */
- protected $dateFormat;
-
- /**
- * The accessors to append to the model's array form.
- *
- * @var array
- */
- protected $appends = [];
-
- /**
- * Indicates whether attributes are snake cased on arrays.
- *
- * @var bool
- */
- public static $snakeAttributes = true;
-
- /**
- * The cache of the mutated attributes for each class.
- *
- * @var array
- */
- protected static $mutatorCache = [];
-
- /**
- * The cache of the "Attribute" return type marked mutated attributes for each class.
- *
- * @var array
- */
- protected static $attributeMutatorCache = [];
-
- /**
- * The cache of the "Attribute" return type marked mutated, gettable attributes for each class.
- *
- * @var array
- */
- protected static $getAttributeMutatorCache = [];
-
- /**
- * The cache of the "Attribute" return type marked mutated, settable attributes for each class.
- *
- * @var array
- */
- protected static $setAttributeMutatorCache = [];
-
- /**
- * The cache of the converted cast types.
- *
- * @var array
- */
- protected static $castTypeCache = [];
-
- /**
- * The encrypter instance that is used to encrypt attributes.
- *
- * @var \Illuminate\Contracts\Encryption\Encrypter|null
- */
- public static $encrypter;
-
- /**
- * Initialize the trait.
- *
- * @return void
- */
- protected function initializeHasAttributes()
- {
- $this->casts = $this->ensureCastsAreStringValues(
- array_merge($this->casts, $this->casts()),
- );
-
- $this->dateFormat ??= static::resolveClassAttribute(Table::class)->dateFormat ?? null;
-
- if (empty($this->appends)) {
- $this->appends = static::resolveClassAttribute(Appends::class, 'columns') ?? [];
- }
- }
-
- /**
- * Convert the model's attributes to an array.
- *
- * @return array
- */
- public function attributesToArray()
- {
- // If an attribute is a date, we will cast it to a string after converting it
- // to a DateTime / Carbon instance. This is so we will get some consistent
- // formatting while accessing attributes vs. arraying / JSONing a model.
- $attributes = $this->addDateAttributesToArray(
- $attributes = $this->getArrayableAttributes()
- );
-
- $attributes = $this->addMutatedAttributesToArray(
- $attributes, $mutatedAttributes = $this->getMutatedAttributes()
- );
-
- // Next we will handle any casts that have been setup for this model and cast
- // the values to their appropriate type. If the attribute has a mutator we
- // will not perform the cast on those attributes to avoid any confusion.
- $attributes = $this->addCastAttributesToArray(
- $attributes, $mutatedAttributes
- );
-
- // Here we will grab all of the appended, calculated attributes to this model
- // as these attributes are not really in the attributes array, but are run
- // when we need to array or JSON the model for convenience to the coder.
- foreach ($this->getArrayableAppends() as $key) {
- $attributes[$key] = $this->mutateAttributeForArray($key, null);
- }
-
- return $attributes;
- }
-
- /**
- * Add the date attributes to the attributes array.
- *
- * @param array $attributes
- * @return array
- */
- protected function addDateAttributesToArray(array $attributes)
- {
- foreach ($this->getDates() as $key) {
- if (is_null($key) || ! isset($attributes[$key])) {
- continue;
- }
-
- $attributes[$key] = $this->serializeDate(
- $this->asDateTime($attributes[$key])
- );
- }
-
- return $attributes;
- }
-
- /**
- * Add the mutated attributes to the attributes array.
- *
- * @param array $attributes
- * @param array $mutatedAttributes
- * @return array
- */
- protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes)
- {
- foreach ($mutatedAttributes as $key) {
- // We want to spin through all the mutated attributes for this model and call
- // the mutator for the attribute. We cache off every mutated attributes so
- // we don't have to constantly check on attributes that actually change.
- if (! array_key_exists($key, $attributes)) {
- continue;
- }
-
- // Next, we will call the mutator for this attribute so that we can get these
- // mutated attribute's actual values. After we finish mutating each of the
- // attributes we will return this final array of the mutated attributes.
- $attributes[$key] = $this->mutateAttributeForArray(
- $key, $attributes[$key]
- );
- }
-
- return $attributes;
- }
-
- /**
- * Add the casted attributes to the attributes array.
- *
- * @param array $attributes
- * @param array $mutatedAttributes
- * @return array
- */
- protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
- {
- foreach ($this->getCasts() as $key => $value) {
- if (! array_key_exists($key, $attributes) ||
- in_array($key, $mutatedAttributes)) {
- continue;
- }
-
- // Here we will cast the attribute. Then, if the cast is a date or datetime cast
- // then we will serialize the date for the array. This will convert the dates
- // to strings based on the date format specified for these Eloquent models.
- $attributes[$key] = $this->castAttribute(
- $key, $attributes[$key]
- );
-
- // If the attribute cast was a date or a datetime, we will serialize the date as
- // a string. This allows the developers to customize how dates are serialized
- // into an array without affecting how they are persisted into the storage.
- if (isset($attributes[$key]) && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) {
- $attributes[$key] = $this->serializeDate($attributes[$key]);
- }
-
- if (isset($attributes[$key]) && ($this->isCustomDateTimeCast($value) ||
- $this->isImmutableCustomDateTimeCast($value))) {
- $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
- }
-
- if ($attributes[$key] instanceof DateTimeInterface &&
- $this->isClassCastable($key)) {
- $attributes[$key] = $this->serializeDate($attributes[$key]);
- }
-
- if (isset($attributes[$key]) && $this->isClassSerializable($key)) {
- $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
- }
-
- if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) {
- $attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($this->getCasts()[$key], $attributes[$key]) : null;
- }
-
- if ($attributes[$key] instanceof Arrayable) {
- $attributes[$key] = $attributes[$key]->toArray();
- }
- }
-
- return $attributes;
- }
-
- /**
- * Get an attribute array of all arrayable attributes.
- *
- * @return array
- */
- protected function getArrayableAttributes()
- {
- return $this->getArrayableItems($this->getAttributes());
- }
-
- /**
- * Get all of the appendable values that are arrayable.
- *
- * @return array
- */
- protected function getArrayableAppends()
- {
- $appends = $this->getAppends();
-
- if (! count($appends)) {
- return [];
- }
-
- return $this->getArrayableItems(
- array_combine($appends, $appends)
- );
- }
-
- /**
- * Get the model's relationships in array form.
- *
- * @return array
- */
- public function relationsToArray()
- {
- $attributes = [];
-
- foreach ($this->getArrayableRelations() as $key => $value) {
- // If the values implement the Arrayable interface we can just call this
- // toArray method on the instances which will convert both models and
- // collections to their proper array form and we'll set the values.
- if ($value instanceof Arrayable) {
- $relation = $value->toArray();
- }
-
- // If the value is null, we'll still go ahead and set it in this list of
- // attributes, since null is used to represent empty relationships if
- // it has a has one or belongs to type relationships on the models.
- elseif (is_null($value)) {
- $relation = $value;
- }
-
- // If the relationships snake-casing is enabled, we will snake case this
- // key so that the relation attribute is snake cased in this returned
- // array to the developers, making this consistent with attributes.
- if (static::$snakeAttributes) {
- $key = Str::snake($key);
- }
-
- // If the relation value has been set, we will set it on this attributes
- // list for returning. If it was not arrayable or null, we'll not set
- // the value on the array because it is some type of invalid value.
- if (array_key_exists('relation', get_defined_vars())) { // check if $relation is in scope (could be null)
- $attributes[$key] = $relation ?? null;
- }
-
- unset($relation);
- }
-
- return $attributes;
- }
-
- /**
- * Get an attribute array of all arrayable relations.
- *
- * @return array
- */
- protected function getArrayableRelations()
- {
- return $this->getArrayableItems($this->relations);
- }
-
- /**
- * Get an attribute array of all arrayable values.
- *
- * @param array $values
- * @return array
- */
- protected function getArrayableItems(array $values)
- {
- if (count($this->getVisible()) > 0) {
- $values = array_intersect_key($values, array_flip($this->getVisible()));
- }
-
- if (count($this->getHidden()) > 0) {
- $values = array_diff_key($values, array_flip($this->getHidden()));
- }
-
- return $values;
- }
-
- /**
- * Determine whether an attribute exists on the model.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttribute($key)
- {
- if (! $key) {
- return false;
- }
-
- return array_key_exists($key, $this->attributes) ||
- array_key_exists($key, $this->casts) ||
- $this->hasGetMutator($key) ||
- $this->hasAttributeMutator($key) ||
- $this->isClassCastable($key);
- }
-
- /**
- * Get an attribute from the model.
- *
- * @param string $key
- * @return mixed
- */
- public function getAttribute($key)
- {
- if (! $key) {
- return;
- }
-
- // If the attribute exists in the attribute array or has a "get" mutator we will
- // get the attribute's value. Otherwise, we will proceed as if the developers
- // are asking for a relationship's value. This covers both types of values.
- if ($this->hasAttribute($key)) {
- return $this->getAttributeValue($key);
- }
-
- // Here we will determine if the model base class itself contains this given key
- // since we don't want to treat any of those methods as relationships because
- // they are all intended as helper methods and none of these are relations.
- if (method_exists(self::class, $key)) {
- return $this->throwMissingAttributeExceptionIfApplicable($key);
- }
-
- return $this->isRelation($key) || $this->relationLoaded($key)
- ? $this->getRelationValue($key)
- : $this->throwMissingAttributeExceptionIfApplicable($key);
- }
-
- /**
- * Either throw a missing attribute exception or return null depending on Eloquent's configuration.
- *
- * @param string $key
- * @return null
- *
- * @throws \Illuminate\Database\Eloquent\MissingAttributeException
- */
- protected function throwMissingAttributeExceptionIfApplicable($key)
- {
- if ($this->exists &&
- ! $this->wasRecentlyCreated &&
- static::preventsAccessingMissingAttributes()) {
- if (isset(static::$missingAttributeViolationCallback)) {
- return call_user_func(static::$missingAttributeViolationCallback, $this, $key);
- }
-
- throw new MissingAttributeException($this, $key);
- }
-
- return null;
- }
-
- /**
- * Get a plain attribute (not a relationship).
- *
- * @param string $key
- * @return mixed
- */
- public function getAttributeValue($key)
- {
- return $this->transformModelValue($key, $this->getAttributeFromArray($key));
- }
-
- /**
- * Get an attribute from the $attributes array.
- *
- * @param string $key
- * @return mixed
- */
- protected function getAttributeFromArray($key)
- {
- $this->mergeAttributeFromCachedCasts($key);
-
- return $this->attributes[$key] ?? null;
- }
-
- /**
- * Get a relationship.
- *
- * @param string $key
- * @return mixed
- */
- public function getRelationValue($key)
- {
- // If the key already exists in the relationships array, it just means the
- // relationship has already been loaded, so we'll just return it out of
- // here because there is no need to query within the relations twice.
- if ($this->relationLoaded($key)) {
- return $this->relations[$key];
- }
-
- if (! $this->isRelation($key)) {
- return;
- }
-
- if ($this->attemptToAutoloadRelation($key)) {
- return $this->relations[$key];
- }
-
- if ($this->preventsLazyLoading) {
- $this->handleLazyLoadingViolation($key);
- }
-
- // If the "attribute" exists as a method on the model, we will just assume
- // it is a relationship and will load and return results from the query
- // and hydrate the relationship's value on the "relationships" array.
- return $this->getRelationshipFromMethod($key);
- }
-
- /**
- * Determine if the given key is a relationship method on the model.
- *
- * @param string $key
- * @return bool
- */
- public function isRelation($key)
- {
- if ($this->hasAttributeMutator($key)) {
- return false;
- }
-
- return method_exists($this, $key) ||
- $this->relationResolver(static::class, $key);
- }
-
- /**
- * Handle a lazy loading violation.
- *
- * @param string $key
- * @return mixed
- *
- * @throws \Illuminate\Database\LazyLoadingViolationException
- */
- protected function handleLazyLoadingViolation($key)
- {
- if (isset(static::$lazyLoadingViolationCallback)) {
- return call_user_func(static::$lazyLoadingViolationCallback, $this, $key);
- }
-
- if (! $this->exists || $this->wasRecentlyCreated) {
- return;
- }
-
- throw new LazyLoadingViolationException($this, $key);
- }
-
- /**
- * Get a relationship value from a method.
- *
- * @param string $method
- * @return mixed
- *
- * @throws \LogicException
- */
- protected function getRelationshipFromMethod($method)
- {
- $relation = $this->$method();
-
- if (! $relation instanceof Relation) {
- if (is_null($relation)) {
- throw new LogicException(sprintf(
- '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
- ));
- }
-
- throw new LogicException(sprintf(
- '%s::%s must return a relationship instance.', static::class, $method
- ));
- }
-
- return tap($relation->getResults(), function ($results) use ($method) {
- $this->setRelation($method, $results);
- });
- }
-
- /**
- * Determine if a get mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasGetMutator($key)
- {
- return method_exists($this, 'get'.Str::studly($key).'Attribute');
- }
-
- /**
- * Determine if a "Attribute" return type marked mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttributeMutator($key)
- {
- if (isset(static::$attributeMutatorCache[get_class($this)][$key])) {
- return static::$attributeMutatorCache[get_class($this)][$key];
- }
-
- if (! method_exists($this, $method = Str::camel($key))) {
- return static::$attributeMutatorCache[get_class($this)][$key] = false;
- }
-
- $returnType = (new ReflectionMethod($this, $method))->getReturnType();
-
- return static::$attributeMutatorCache[get_class($this)][$key] =
- $returnType instanceof ReflectionNamedType &&
- $returnType->getName() === Attribute::class;
- }
-
- /**
- * Determine if a "Attribute" return type marked get mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttributeGetMutator($key)
- {
- if (isset(static::$getAttributeMutatorCache[get_class($this)][$key])) {
- return static::$getAttributeMutatorCache[get_class($this)][$key];
- }
-
- if (! $this->hasAttributeMutator($key)) {
- return static::$getAttributeMutatorCache[get_class($this)][$key] = false;
- }
-
- return static::$getAttributeMutatorCache[get_class($this)][$key] = is_callable($this->{Str::camel($key)}()->get);
- }
-
- /**
- * Determine if any get mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAnyGetMutator($key)
- {
- return $this->hasGetMutator($key) || $this->hasAttributeGetMutator($key);
- }
-
- /**
- * Get the value of an attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function mutateAttribute($key, $value)
- {
- $this->mergeAttributesFromCachedCasts();
-
- return $this->{'get'.Str::studly($key).'Attribute'}($value);
- }
-
- /**
- * Get the value of an "Attribute" return type marked attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function mutateAttributeMarkedAttribute($key, $value)
- {
- if (array_key_exists($key, $this->attributeCastCache)) {
- return $this->attributeCastCache[$key];
- }
-
- $this->mergeAttributesFromCachedCasts();
-
- $attribute = $this->{Str::camel($key)}();
-
- $value = call_user_func($attribute->get ?: function ($value) {
- return $value;
- }, $value, $this->attributes);
-
- if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
- $this->attributeCastCache[$key] = $value;
- } else {
- unset($this->attributeCastCache[$key]);
- }
-
- return $value;
- }
-
- /**
- * Get the value of an attribute using its mutator for array conversion.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function mutateAttributeForArray($key, $value)
- {
- if ($this->isClassCastable($key)) {
- $value = $this->getClassCastableAttributeValue($key, $value);
- } elseif (isset(static::$getAttributeMutatorCache[get_class($this)][$key]) &&
- static::$getAttributeMutatorCache[get_class($this)][$key] === true) {
- $value = $this->mutateAttributeMarkedAttribute($key, $value);
-
- $value = $value instanceof DateTimeInterface
- ? $this->serializeDate($value)
- : $value;
- } else {
- $value = $this->mutateAttribute($key, $value);
- }
-
- return $value instanceof Arrayable ? $value->toArray() : $value;
- }
-
- /**
- * Merge new casts with existing casts on the model.
- *
- * @param array $casts
- * @return $this
- */
- public function mergeCasts($casts)
- {
- $casts = $this->ensureCastsAreStringValues($casts);
-
- $this->casts = array_merge($this->casts, $casts);
-
- return $this;
- }
-
- /**
- * Ensure that the given casts are strings.
- *
- * @param array $casts
- * @return array
- *
- * @throws \InvalidArgumentException
- */
- protected function ensureCastsAreStringValues($casts)
- {
- foreach ($casts as $attribute => $cast) {
- $casts[$attribute] = match (true) {
- is_object($cast) => value(function () use ($cast, $attribute) {
- if ($cast instanceof Stringable) {
- return (string) $cast;
- }
-
- throw new InvalidArgumentException(
- "The cast object for the {$attribute} attribute must implement Stringable."
- );
- }),
- is_array($cast) => value(function () use ($cast) {
- if (count($cast) === 1) {
- return $cast[0];
- }
-
- [$cast, $arguments] = [array_shift($cast), $cast];
-
- return $cast.':'.implode(',', $arguments);
- }),
- default => $cast,
- };
- }
-
- return $casts;
- }
-
- /**
- * Cast an attribute to a native PHP type.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function castAttribute($key, $value)
- {
- $castType = $this->getCastType($key);
-
- if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) {
- return $value;
- }
-
- // If the key is one of the encrypted castable types, we'll first decrypt
- // the value and update the cast type so we may leverage the following
- // logic for casting this value to any additionally specified types.
- if ($this->isEncryptedCastable($key)) {
- $value = $this->fromEncryptedString($value);
-
- $castType = Str::after($castType, 'encrypted:');
- }
-
- switch ($castType) {
- case 'int':
- case 'integer':
- return (int) $value;
- case 'real':
- case 'float':
- case 'double':
- return $this->fromFloat($value);
- case 'decimal':
- return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]);
- case 'string':
- return (string) $value;
- case 'bool':
- case 'boolean':
- return (bool) $value;
- case 'object':
- return $this->fromJson($value, true);
- case 'array':
- case 'json':
- case 'json:unicode':
- return $this->fromJson($value);
- case 'collection':
- return new BaseCollection($this->fromJson($value));
- case 'date':
- return $this->asDate($value);
- case 'datetime':
- case 'custom_datetime':
- return $this->asDateTime($value);
- case 'immutable_date':
- return $this->asDate($value)->toImmutable();
- case 'immutable_custom_datetime':
- case 'immutable_datetime':
- return $this->asDateTime($value)->toImmutable();
- case 'timestamp':
- return $this->asTimestamp($value);
- }
-
- if ($this->isEnumCastable($key)) {
- return $this->getEnumCastableAttributeValue($key, $value);
- }
-
- if ($this->isClassCastable($key)) {
- return $this->getClassCastableAttributeValue($key, $value);
- }
-
- return $value;
- }
-
- /**
- * Cast the given attribute using a custom cast class.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function getClassCastableAttributeValue($key, $value)
- {
- $caster = $this->resolveCasterClass($key);
-
- $objectCachingDisabled = $caster->withoutObjectCaching ?? false;
-
- if (isset($this->classCastCache[$key]) && ! $objectCachingDisabled) {
- return $this->classCastCache[$key];
- } else {
- $value = $caster instanceof CastsInboundAttributes
- ? $value
- : $caster->get($this, $key, $value, $this->attributes);
-
- if ($caster instanceof CastsInboundAttributes ||
- ! is_object($value) ||
- $objectCachingDisabled) {
- unset($this->classCastCache[$key]);
- } else {
- $this->classCastCache[$key] = $value;
- }
-
- return $value;
- }
- }
-
- /**
- * Cast the given attribute to an enum.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function getEnumCastableAttributeValue($key, $value)
- {
- if (is_null($value)) {
- return;
- }
-
- $castType = $this->getCasts()[$key];
-
- if ($value instanceof $castType) {
- return $value;
- }
-
- return $this->getEnumCaseFromValue($castType, $value);
- }
-
- /**
- * Get the type of cast for a model attribute.
- *
- * @param string $key
- * @return string
- */
- protected function getCastType($key)
- {
- $castType = $this->getCasts()[$key];
-
- if (isset(static::$castTypeCache[$castType])) {
- return static::$castTypeCache[$castType];
- }
-
- if ($this->isCustomDateTimeCast($castType)) {
- $convertedCastType = 'custom_datetime';
- } elseif ($this->isImmutableCustomDateTimeCast($castType)) {
- $convertedCastType = 'immutable_custom_datetime';
- } elseif ($this->isDecimalCast($castType)) {
- $convertedCastType = 'decimal';
- } elseif (class_exists($castType)) {
- $convertedCastType = $castType;
- } else {
- $convertedCastType = trim(strtolower($castType));
- }
-
- return static::$castTypeCache[$castType] = $convertedCastType;
- }
-
- /**
- * Increment or decrement the given attribute using the custom cast class.
- *
- * @param string $method
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function deviateClassCastableAttribute($method, $key, $value)
- {
- return $this->resolveCasterClass($key)->{$method}(
- $this, $key, $value, $this->attributes
- );
- }
-
- /**
- * Serialize the given attribute using the custom cast class.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function serializeClassCastableAttribute($key, $value)
- {
- return $this->resolveCasterClass($key)->serialize(
- $this, $key, $value, $this->attributes
- );
- }
-
- /**
- * Compare two values for the given attribute using the custom cast class.
- *
- * @param string $key
- * @param mixed $original
- * @param mixed $value
- * @return bool
- */
- protected function compareClassCastableAttribute($key, $original, $value)
- {
- return $this->resolveCasterClass($key)->compare(
- $this, $key, $original, $value
- );
- }
-
- /**
- * Determine if the cast type is a custom date time cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isCustomDateTimeCast($cast)
- {
- return str_starts_with($cast, 'date:') ||
- str_starts_with($cast, 'datetime:');
- }
-
- /**
- * Determine if the cast type is an immutable custom date time cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isImmutableCustomDateTimeCast($cast)
- {
- return str_starts_with($cast, 'immutable_date:') ||
- str_starts_with($cast, 'immutable_datetime:');
- }
-
- /**
- * Determine if the cast type is a decimal cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isDecimalCast($cast)
- {
- return str_starts_with($cast, 'decimal:');
- }
-
- /**
- * Set a given attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- public function setAttribute($key, $value)
- {
- // First we will check for the presence of a mutator for the set operation
- // which simply lets the developers tweak the attribute as it is set on
- // this model, such as "json_encoding" a listing of data for storage.
- if ($this->hasSetMutator($key)) {
- return $this->setMutatedAttributeValue($key, $value);
- } elseif ($this->hasAttributeSetMutator($key)) {
- return $this->setAttributeMarkedMutatedAttributeValue($key, $value);
- }
-
- // If an attribute is listed as a "date", we'll convert it from a DateTime
- // instance into a form proper for storage on the database tables using
- // the connection grammar's date format. We will auto set the values.
- elseif (! is_null($value) && $this->isDateAttribute($key)) {
- $value = $this->fromDateTime($value);
- }
-
- if ($this->isEnumCastable($key)) {
- $this->setEnumCastableAttribute($key, $value);
-
- return $this;
- }
-
- if ($this->isClassCastable($key)) {
- $this->setClassCastableAttribute($key, $value);
-
- return $this;
- }
-
- if (! is_null($value) && $this->isJsonCastable($key)) {
- $value = $this->castAttributeAsJson($key, $value);
- }
-
- // If this attribute contains a JSON ->, we'll set the proper value in the
- // attribute's underlying array. This takes care of properly nesting an
- // attribute in the array's value in the case of deeply nested items.
- if (str_contains($key, '->')) {
- return $this->fillJsonAttribute($key, $value);
- }
-
- if (! is_null($value) && $this->isEncryptedCastable($key)) {
- $value = $this->castAttributeAsEncryptedString($key, $value);
- }
-
- if (! is_null($value) && $this->hasCast($key, 'hashed')) {
- $value = $this->castAttributeAsHashedString($key, $value);
- }
-
- $this->attributes[$key] = $value;
-
- return $this;
- }
-
- /**
- * Determine if a set mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasSetMutator($key)
- {
- return method_exists($this, 'set'.Str::studly($key).'Attribute');
- }
-
- /**
- * Determine if an "Attribute" return type marked set mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttributeSetMutator($key)
- {
- $class = get_class($this);
-
- if (isset(static::$setAttributeMutatorCache[$class][$key])) {
- return static::$setAttributeMutatorCache[$class][$key];
- }
-
- if (! method_exists($this, $method = Str::camel($key))) {
- return static::$setAttributeMutatorCache[$class][$key] = false;
- }
-
- $returnType = (new ReflectionMethod($this, $method))->getReturnType();
-
- return static::$setAttributeMutatorCache[$class][$key] =
- $returnType instanceof ReflectionNamedType &&
- $returnType->getName() === Attribute::class &&
- is_callable($this->{$method}()->set);
- }
-
- /**
- * Set the value of an attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function setMutatedAttributeValue($key, $value)
- {
- $this->mergeAttributesFromCachedCasts();
-
- return $this->{'set'.Str::studly($key).'Attribute'}($value);
- }
-
- /**
- * Set the value of a "Attribute" return type marked attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function setAttributeMarkedMutatedAttributeValue($key, $value)
- {
- $this->mergeAttributesFromCachedCasts();
-
- $attribute = $this->{Str::camel($key)}();
-
- $callback = $attribute->set ?: function ($value) use ($key) {
- $this->attributes[$key] = $value;
- };
-
- $this->attributes = array_merge(
- $this->attributes,
- $this->normalizeCastClassResponse(
- $key, $callback($value, $this->attributes)
- )
- );
-
- if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
- $this->attributeCastCache[$key] = $value;
- } else {
- unset($this->attributeCastCache[$key]);
- }
-
- return $this;
- }
-
- /**
- * Determine if the given attribute is a date or date castable.
- *
- * @param string $key
- * @return bool
- */
- protected function isDateAttribute($key)
- {
- return in_array($key, $this->getDates(), true) ||
- $this->isDateCastable($key);
- }
-
- /**
- * Set a given JSON attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- * @return $this
- */
- public function fillJsonAttribute($key, $value)
- {
- [$key, $path] = explode('->', $key, 2);
-
- $value = $this->asJson($this->getArrayAttributeWithValue(
- $path, $key, $value
- ), $this->getJsonCastFlags($key));
-
- $this->attributes[$key] = $this->isEncryptedCastable($key)
- ? $this->castAttributeAsEncryptedString($key, $value)
- : $value;
-
- if ($this->isClassCastable($key)) {
- unset($this->classCastCache[$key]);
- }
-
- return $this;
- }
-
- /**
- * Set the value of a class castable attribute.
- *
- * @param string $key
- * @param mixed $value
- * @return void
- */
- protected function setClassCastableAttribute($key, $value)
- {
- $caster = $this->resolveCasterClass($key);
-
- $this->attributes = array_replace(
- $this->attributes,
- $this->normalizeCastClassResponse($key, $caster->set(
- $this, $key, $value, $this->attributes
- ))
- );
-
- if ($caster instanceof CastsInboundAttributes ||
- ! is_object($value) ||
- ($caster->withoutObjectCaching ?? false)) {
- unset($this->classCastCache[$key]);
- } else {
- $this->classCastCache[$key] = $value;
- }
- }
-
- /**
- * Set the value of an enum castable attribute.
- *
- * @param string $key
- * @param \UnitEnum|string|int|null $value
- * @return void
- */
- protected function setEnumCastableAttribute($key, $value)
- {
- $enumClass = $this->getCasts()[$key];
-
- if (! isset($value)) {
- $this->attributes[$key] = null;
- } elseif (is_object($value)) {
- $this->attributes[$key] = $this->getStorableEnumValue($enumClass, $value);
- } else {
- $this->attributes[$key] = $this->getStorableEnumValue(
- $enumClass, $this->getEnumCaseFromValue($enumClass, $value)
- );
- }
- }
-
- /**
- * Get an enum case instance from a given class and value.
- *
- * @param string $enumClass
- * @param string|int $value
- * @return \UnitEnum
- */
- protected function getEnumCaseFromValue($enumClass, $value)
- {
- return is_subclass_of($enumClass, BackedEnum::class)
- ? $enumClass::from($value)
- : constant($enumClass.'::'.$value);
- }
-
- /**
- * Get the storable value from the given enum.
- *
- * @param string $expectedEnum
- * @param \UnitEnum $value
- * @return string|int
- *
- * @throws \ValueError
- */
- protected function getStorableEnumValue($expectedEnum, $value)
- {
- if (! $value instanceof $expectedEnum) {
- throw new ValueError(sprintf('Value [%s] is not of the expected enum type [%s].', var_export($value, true), $expectedEnum));
- }
-
- return enum_value($value);
- }
-
- /**
- * Get an array attribute with the given key and value set.
- *
- * @param string $path
- * @param string $key
- * @param mixed $value
- * @return array
- */
- protected function getArrayAttributeWithValue($path, $key, $value)
- {
- return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) {
- Arr::set($array, str_replace('->', '.', $path), $value);
- });
- }
-
- /**
- * Get an array attribute or return an empty array if it is not set.
- *
- * @param string $key
- * @return array
- */
- protected function getArrayAttributeByKey($key)
- {
- if (! isset($this->attributes[$key])) {
- return [];
- }
-
- return $this->fromJson(
- $this->isEncryptedCastable($key)
- ? $this->fromEncryptedString($this->attributes[$key])
- : $this->attributes[$key]
- );
- }
-
- /**
- * Cast the given attribute to JSON.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- *
- * @throws \Illuminate\Database\Eloquent\JsonEncodingException
- */
- protected function castAttributeAsJson($key, $value)
- {
- $value = $this->asJson($value, $this->getJsonCastFlags($key));
-
- if ($value === false) {
- throw JsonEncodingException::forAttribute(
- $this, $key, json_last_error_msg()
- );
- }
-
- return $value;
- }
-
- /**
- * Get the JSON casting flags for the given attribute.
- *
- * @param string $key
- * @return int
- */
- protected function getJsonCastFlags($key)
- {
- $flags = 0;
-
- if ($this->hasCast($key, ['json:unicode'])) {
- $flags |= JSON_UNESCAPED_UNICODE;
- }
-
- return $flags;
- }
-
- /**
- * Encode the given value as JSON.
- *
- * @param mixed $value
- * @param int $flags
- * @return string
- */
- protected function asJson($value, $flags = 0)
- {
- return Json::encode($value, $flags);
- }
-
- /**
- * Decode the given JSON back into an array or object.
- *
- * @param string|null $value
- * @param bool $asObject
- * @return mixed
- */
- public function fromJson($value, $asObject = false)
- {
- if ($value === null || $value === '') {
- return null;
- }
-
- return Json::decode($value, ! $asObject);
- }
-
- /**
- * Decrypt the given encrypted string.
- *
- * @param string $value
- * @return mixed
- */
- public function fromEncryptedString($value)
- {
- return static::currentEncrypter()->decrypt($value, false);
- }
-
- /**
- * Cast the given attribute to an encrypted string.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- */
- protected function castAttributeAsEncryptedString($key, #[\SensitiveParameter] $value)
- {
- return static::currentEncrypter()->encrypt($value, false);
- }
-
- /**
- * Set the encrypter instance that will be used to encrypt attributes.
- *
- * @param \Illuminate\Contracts\Encryption\Encrypter|null $encrypter
- * @return void
- */
- public static function encryptUsing($encrypter)
- {
- static::$encrypter = $encrypter;
- }
-
- /**
- * Get the current encrypter being used by the model.
- *
- * @return \Illuminate\Contracts\Encryption\Encrypter
- */
- public static function currentEncrypter()
- {
- return static::$encrypter ?? Crypt::getFacadeRoot();
- }
-
- /**
- * Cast the given attribute to a hashed string.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- *
- * @throws \RuntimeException
- */
- protected function castAttributeAsHashedString($key, #[\SensitiveParameter] $value)
- {
- if ($value === null) {
- return null;
- }
-
- if (! Hash::isHashed($value)) {
- return Hash::make($value);
- }
-
- /** @phpstan-ignore staticMethod.notFound */
- if (! Hash::verifyConfiguration($value)) {
- throw new RuntimeException("Could not verify the hashed value's configuration.");
- }
-
- return $value;
- }
-
- /**
- * Decode the given float.
- *
- * @param mixed $value
- * @return mixed
- */
- public function fromFloat($value)
- {
- return match ((string) $value) {
- 'Infinity' => INF,
- '-Infinity' => -INF,
- 'NaN' => NAN,
- default => (float) $value,
- };
- }
-
- /**
- * Return a decimal as string.
- *
- * @param float|string $value
- * @param int $decimals
- * @return string
- *
- * @throws \Illuminate\Support\Exceptions\MathException
- */
- protected function asDecimal($value, $decimals)
- {
- try {
- return (string) BigDecimal::of((string) $value)->toScale($decimals, RoundingMode::HALF_UP);
- } catch (BrickMathException $e) {
- throw new MathException('Unable to cast value to a decimal.', previous: $e);
- }
- }
-
- /**
- * Return a timestamp as DateTime object with time set to 00:00:00.
- *
- * @param mixed $value
- * @return \Illuminate\Support\Carbon
- */
- protected function asDate($value)
- {
- return $this->asDateTime($value)->startOfDay();
- }
-
- /**
- * Return a timestamp as DateTime object.
- *
- * @param mixed $value
- * @return \Illuminate\Support\Carbon
- */
- protected function asDateTime($value)
- {
- // If this value is already a Carbon instance, we shall just return it as is.
- // This prevents us having to re-instantiate a Carbon instance when we know
- // it already is one, which wouldn't be fulfilled by the DateTime check.
- if ($value instanceof CarbonInterface) {
- return Date::instance($value);
- }
-
- // If the value is already a DateTime instance, we will just skip the rest of
- // these checks since they will be a waste of time, and hinder performance
- // when checking the field. We will just return the DateTime right away.
- if ($value instanceof DateTimeInterface) {
- return Date::parse(
- $value->format('Y-m-d H:i:s.u'), $value->getTimezone()
- );
- }
-
- // If this value is an integer, we will assume it is a UNIX timestamp's value
- // and format a Carbon object from this timestamp. This allows flexibility
- // when defining your date fields as they might be UNIX timestamps here.
- if (is_numeric($value)) {
- return Date::createFromTimestamp($value, date_default_timezone_get());
- }
-
- // If the value is in simply year, month, day format, we will instantiate the
- // Carbon instances from that format. Again, this provides for simple date
- // fields on the database, while still supporting Carbonized conversion.
- if ($this->isStandardDateFormat($value)) {
- return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay());
- }
-
- $format = $this->getDateFormat();
-
- // Finally, we will just assume this date is in the format used by default on
- // the database connection and use that format to create the Carbon object
- // that is returned back out to the developers after we convert it here.
- try {
- $date = Date::createFromFormat($format, $value);
- } catch (InvalidArgumentException) {
- $date = false;
- }
-
- return $date ?: Date::parse($value);
- }
-
- /**
- * Determine if the given value is a standard date format.
- *
- * @param string $value
- * @return bool
- */
- protected function isStandardDateFormat($value)
- {
- return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
- }
-
- /**
- * Convert a DateTime to a storable string.
- *
- * @param mixed $value
- * @return string|null
- */
- public function fromDateTime($value)
- {
- return empty($value) ? $value : $this->asDateTime($value)->format(
- $this->getDateFormat()
- );
- }
-
- /**
- * Return a timestamp as unix timestamp.
- *
- * @param mixed $value
- * @return int
- */
- protected function asTimestamp($value)
- {
- return $this->asDateTime($value)->getTimestamp();
- }
-
- /**
- * Prepare a date for array / JSON serialization.
- *
- * @param \DateTimeInterface $date
- * @return string
- */
- protected function serializeDate(DateTimeInterface $date)
- {
- return $date instanceof DateTimeImmutable ?
- CarbonImmutable::instance($date)->toJSON() :
- Carbon::instance($date)->toJSON();
- }
-
- /**
- * Get the attributes that should be converted to dates.
- *
- * @return array
- */
- public function getDates()
- {
- return $this->usesTimestamps() ? [
- $this->getCreatedAtColumn(),
- $this->getUpdatedAtColumn(),
- ] : [];
- }
-
- /**
- * Get the format for database stored dates.
- *
- * @return string
- */
- public function getDateFormat()
- {
- return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
- }
-
- /**
- * Set the date format used by the model.
- *
- * @param string $format
- * @return $this
- */
- public function setDateFormat($format)
- {
- $this->dateFormat = $format;
-
- return $this;
- }
-
- /**
- * Determine whether an attribute should be cast to a native type.
- *
- * @param string $key
- * @param array|string|null $types
- * @return bool
- */
- public function hasCast($key, $types = null)
- {
- if (array_key_exists($key, $this->getCasts())) {
- return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
- }
-
- return false;
- }
-
- /**
- * Get the attributes that should be cast.
- *
- * @return array
- */
- public function getCasts()
- {
- if ($this->getIncrementing()) {
- return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
- }
-
- return $this->casts;
- }
-
- /**
- * Get the attributes that should be cast.
- *
- * @return array
- */
- protected function casts()
- {
- return [];
- }
-
- /**
- * Determine whether a value is Date / DateTime castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isDateCastable($key)
- {
- return $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']);
- }
-
- /**
- * Determine whether a value is Date / DateTime custom-castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isDateCastableWithCustomFormat($key)
- {
- return $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']);
- }
-
- /**
- * Determine whether a value is JSON castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isJsonCastable($key)
- {
- return $this->hasCast($key, ['array', 'json', 'json:unicode', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
- }
-
- /**
- * Determine whether a value is an encrypted castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isEncryptedCastable($key)
- {
- return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
- }
-
- /**
- * Determine if the given key is cast using a custom class.
- *
- * @param string $key
- * @return bool
- *
- * @throws \Illuminate\Database\Eloquent\InvalidCastException
- */
- protected function isClassCastable($key)
- {
- $casts = $this->getCasts();
-
- if (! array_key_exists($key, $casts)) {
- return false;
- }
-
- $castType = $this->parseCasterClass($casts[$key]);
-
- if (in_array($castType, static::$primitiveCastTypes)) {
- return false;
- }
-
- if (class_exists($castType)) {
- return true;
- }
-
- throw new InvalidCastException($this->getModel(), $key, $castType);
- }
-
- /**
- * Determine if the given key is cast using an enum.
- *
- * @param string $key
- * @return bool
- */
- protected function isEnumCastable($key)
- {
- $casts = $this->getCasts();
-
- if (! array_key_exists($key, $casts)) {
- return false;
- }
-
- $castType = $casts[$key];
-
- if (in_array($castType, static::$primitiveCastTypes)) {
- return false;
- }
-
- if (is_subclass_of($castType, Castable::class)) {
- return false;
- }
-
- return enum_exists($castType);
- }
-
- /**
- * Determine if the key is deviable using a custom class.
- *
- * @param string $key
- * @return bool
- *
- * @throws \Illuminate\Database\Eloquent\InvalidCastException
- */
- protected function isClassDeviable($key)
- {
- if (! $this->isClassCastable($key)) {
- return false;
- }
-
- $castType = $this->resolveCasterClass($key);
-
- return method_exists($castType::class, 'increment') && method_exists($castType::class, 'decrement');
- }
-
- /**
- * Determine if the key is serializable using a custom class.
- *
- * @param string $key
- * @return bool
- *
- * @throws \Illuminate\Database\Eloquent\InvalidCastException
- */
- protected function isClassSerializable($key)
- {
- return ! $this->isEnumCastable($key) &&
- $this->isClassCastable($key) &&
- method_exists($this->resolveCasterClass($key), 'serialize');
- }
-
- /**
- * Determine if the key is comparable using a custom class.
- *
- * @param string $key
- * @return bool
- */
- protected function isClassComparable($key)
- {
- return ! $this->isEnumCastable($key) &&
- $this->isClassCastable($key) &&
- method_exists($this->resolveCasterClass($key), 'compare');
- }
-
- /**
- * Resolve the custom caster class for a given key.
- *
- * @param string $key
- * @return mixed
- */
- protected function resolveCasterClass($key)
- {
- $castType = $this->getCasts()[$key];
-
- $arguments = [];
-
- if (is_string($castType) && str_contains($castType, ':')) {
- $segments = explode(':', $castType, 2);
-
- $castType = $segments[0];
- $arguments = explode(',', $segments[1]);
- }
-
- if (is_subclass_of($castType, Castable::class)) {
- $castType = $castType::castUsing($arguments);
- }
-
- if (is_object($castType)) {
- return $castType;
- }
-
- return new $castType(...$arguments);
- }
-
- /**
- * Parse the given caster class, removing any arguments.
- *
- * @param string $class
- * @return string
- */
- protected function parseCasterClass($class)
- {
- return ! str_contains($class, ':')
- ? $class
- : explode(':', $class, 2)[0];
- }
-
- /**
- * Merge the cast class and attribute cast attributes back into the model.
- *
- * @return void
- */
- protected function mergeAttributesFromCachedCasts()
- {
- $this->mergeAttributesFromClassCasts();
- $this->mergeAttributesFromAttributeCasts();
- }
-
- /**
- * Merge the a cast class and attribute cast attribute back into the model.
- *
- * @return void
- */
- protected function mergeAttributeFromCachedCasts(string $key)
- {
- $this->mergeAttributeFromClassCasts($key);
- $this->mergeAttributeFromAttributeCasts($key);
- }
-
- /**
- * Merge the cast class attributes back into the model.
- *
- * @return void
- */
- protected function mergeAttributesFromClassCasts()
- {
- foreach ($this->classCastCache as $key => $value) {
- $this->mergeAttributeFromClassCasts($key);
- }
- }
-
- /**
- * Merge the cast class attribute back into the model.
- *
- * @return void
- */
- protected function mergeAttributeFromClassCasts(string $key): void
- {
- if (! isset($this->classCastCache[$key])) {
- return;
- }
-
- $value = $this->classCastCache[$key];
-
- $caster = $this->resolveCasterClass($key);
-
- $this->attributes = array_merge(
- $this->attributes,
- $caster instanceof CastsInboundAttributes
- ? [$key => $value]
- : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes))
- );
- }
-
- /**
- * Merge the cast class attributes back into the model.
- *
- * @return void
- */
- protected function mergeAttributesFromAttributeCasts()
- {
- foreach ($this->attributeCastCache as $key => $value) {
- $this->mergeAttributeFromAttributeCasts($key);
- }
- }
-
- /**
- * Merge the cast class attribute back into the model.
- *
- * @return void
- */
- protected function mergeAttributeFromAttributeCasts(string $key): void
- {
- if (! isset($this->attributeCastCache[$key])) {
- return;
- }
-
- $value = $this->attributeCastCache[$key];
-
- $attribute = $this->{Str::camel($key)}();
-
- if ($attribute->get && ! $attribute->set) {
- return;
- }
-
- $callback = $attribute->set ?: function ($value) use ($key) {
- $this->attributes[$key] = $value;
- };
-
- $this->attributes = array_merge(
- $this->attributes,
- $this->normalizeCastClassResponse(
- $key, $callback($value, $this->attributes)
- )
- );
- }
-
- /**
- * Normalize the response from a custom class caster.
- *
- * @param string $key
- * @param mixed $value
- * @return array
- */
- protected function normalizeCastClassResponse($key, $value)
- {
- return is_array($value) ? $value : [$key => $value];
- }
-
- /**
- * Get all of the current attributes on the model.
- *
- * @return array
- */
- public function getAttributes()
- {
- $this->mergeAttributesFromCachedCasts();
-
- return $this->attributes;
- }
-
- /**
- * Get all of the current attributes on the model for an insert operation.
- *
- * @return array
- */
- protected function getAttributesForInsert()
- {
- return $this->getAttributes();
- }
-
- /**
- * Set the array of model attributes. No checking is done.
- *
- * @param array $attributes
- * @param bool $sync
- * @return $this
- */
- public function setRawAttributes(array $attributes, $sync = false)
- {
- $this->attributes = $attributes;
-
- if ($sync) {
- $this->syncOriginal();
- }
-
- $this->classCastCache = [];
- $this->attributeCastCache = [];
-
- return $this;
- }
-
- /**
- * Get the model's original attribute values.
- *
- * @param string|null $key
- * @param mixed $default
- * @return ($key is null ? array : mixed)
- */
- public function getOriginal($key = null, $default = null)
- {
- return (new static)->setRawAttributes(
- $this->original, $sync = true
- )->getOriginalWithoutRewindingModel($key, $default);
- }
-
- /**
- * Get the model's original attribute values.
- *
- * @param string|null $key
- * @param mixed $default
- * @return ($key is null ? array : mixed)
- */
- protected function getOriginalWithoutRewindingModel($key = null, $default = null)
- {
- if ($key) {
- return $this->transformModelValue(
- $key, Arr::get($this->original, $key, $default)
- );
- }
-
- return (new Collection($this->original))
- ->mapWithKeys(fn ($value, $key) => [$key => $this->transformModelValue($key, $value)])
- ->all();
- }
-
- /**
- * Get the model's raw original attribute values.
- *
- * @param string|null $key
- * @param mixed $default
- * @return ($key is null ? array : mixed)
- */
- public function getRawOriginal($key = null, $default = null)
- {
- return Arr::get($this->original, $key, $default);
- }
-
- /**
- * Get a subset of the model's attributes.
- *
- * @param array|mixed $attributes
- * @return array
- */
- public function only($attributes)
- {
- $results = [];
-
- foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) {
- $results[$attribute] = $this->getAttribute($attribute);
- }
-
- return $results;
- }
-
- /**
- * Get all attributes except the given ones.
- *
- * @param array|mixed $attributes
- * @return array
- */
- public function except($attributes)
- {
- $attributes = is_array($attributes) ? $attributes : func_get_args();
-
- $results = [];
-
- foreach ($this->getAttributes() as $key => $value) {
- if (! in_array($key, $attributes)) {
- $results[$key] = $this->getAttribute($key);
- }
- }
-
- return $results;
- }
-
- /**
- * Sync the original attributes with the current.
- *
- * @return $this
- */
- public function syncOriginal()
- {
- $this->original = $this->getAttributes();
-
- return $this;
- }
-
- /**
- * Sync a single original attribute with its current value.
- *
- * @param string $attribute
- * @return $this
- */
- public function syncOriginalAttribute($attribute)
- {
- return $this->syncOriginalAttributes($attribute);
- }
-
- /**
- * Sync multiple original attribute with their current values.
- *
- * @param array|string $attributes
- * @return $this
- */
- public function syncOriginalAttributes($attributes)
- {
- $attributes = is_array($attributes) ? $attributes : func_get_args();
-
- $modelAttributes = $this->getAttributes();
-
- foreach ($attributes as $attribute) {
- $this->original[$attribute] = $modelAttributes[$attribute];
- }
-
- return $this;
- }
-
- /**
- * Sync the changed attributes.
- *
- * @return $this
- */
- public function syncChanges()
- {
- $this->changes = $this->getDirty();
- $this->previous = array_intersect_key($this->getRawOriginal(), $this->changes);
-
- return $this;
- }
-
- /**
- * Determine if the model or any of the given attribute(s) have been modified.
- *
- * @param array|string|null $attributes
- * @return bool
- */
- public function isDirty($attributes = null)
- {
- return $this->hasChanges(
- $this->getDirty(), is_array($attributes) ? $attributes : func_get_args()
- );
- }
-
- /**
- * Determine if the model or all the given attribute(s) have remained the same.
- *
- * @param array|string|null $attributes
- * @return bool
- */
- public function isClean($attributes = null)
- {
- return ! $this->isDirty(...func_get_args());
- }
-
- /**
- * Discard attribute changes and reset the attributes to their original state.
- *
- * @return $this
- */
- public function discardChanges()
- {
- [$this->attributes, $this->changes, $this->previous] = [$this->original, [], []];
-
- $this->classCastCache = [];
- $this->attributeCastCache = [];
-
- return $this;
- }
-
- /**
- * Determine if the model or any of the given attribute(s) were changed when the model was last saved.
- *
- * @param array|string|null $attributes
- * @return bool
- */
- public function wasChanged($attributes = null)
- {
- return $this->hasChanges(
- $this->getChanges(), is_array($attributes) ? $attributes : func_get_args()
- );
- }
-
- /**
- * Determine if any of the given attributes were changed when the model was last saved.
- *
- * @param array $changes
- * @param array|string|null $attributes
- * @return bool
- */
- protected function hasChanges($changes, $attributes = null)
- {
- // If no specific attributes were provided, we will just see if the dirty array
- // already contains any attributes. If it does we will just return that this
- // count is greater than zero. Else, we need to check specific attributes.
- if (empty($attributes)) {
- return count($changes) > 0;
- }
-
- // Here we will spin through every attribute and see if this is in the array of
- // dirty attributes. If it is, we will return true and if we make it through
- // all of the attributes for the entire array we will return false at end.
- foreach (Arr::wrap($attributes) as $attribute) {
- if (array_key_exists($attribute, $changes)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Get the attributes that have been changed since the last sync.
- *
- * @return array
- */
- public function getDirty()
- {
- $dirty = [];
-
- foreach ($this->getAttributes() as $key => $value) {
- if (! $this->originalIsEquivalent($key)) {
- $dirty[$key] = $value;
- }
- }
-
- return $dirty;
- }
-
- /**
- * Get the attributes that have been changed since the last sync for an update operation.
- *
- * @return array
- */
- protected function getDirtyForUpdate()
- {
- return $this->getDirty();
- }
-
- /**
- * Get the attributes that were changed when the model was last saved.
- *
- * @return array
- */
- public function getChanges()
- {
- return $this->changes;
- }
-
- /**
- * Get the attributes that were previously original before the model was last saved.
- *
- * @return array
- */
- public function getPrevious()
- {
- return $this->previous;
- }
-
- /**
- * Determine if the new and old values for a given key are equivalent.
- *
- * @param string $key
- * @return bool
- */
- public function originalIsEquivalent($key)
- {
- if (! array_key_exists($key, $this->original)) {
- return false;
- }
-
- $attribute = Arr::get($this->attributes, $key);
- $original = Arr::get($this->original, $key);
-
- if ($attribute === $original) {
- return true;
- } elseif (is_null($attribute)) {
- return false;
- } elseif ($this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key)) {
- return $this->fromDateTime($attribute) ===
- $this->fromDateTime($original);
- } elseif ($this->hasCast($key, ['object', 'collection'])) {
- return $this->fromJson($attribute) ===
- $this->fromJson($original);
- } elseif ($this->hasCast($key, ['real', 'float', 'double'])) {
- if ($original === null) {
- return false;
- }
-
- return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4;
- } elseif ($this->isEncryptedCastable($key) && ! empty(static::currentEncrypter()->getPreviousKeys())) {
- return false;
- } elseif ($this->hasCast($key, static::$primitiveCastTypes)) {
- return $this->castAttribute($key, $attribute) ===
- $this->castAttribute($key, $original);
- } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) {
- return $this->fromJson($attribute) === $this->fromJson($original);
- } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsEnumArrayObject::class, AsEnumCollection::class])) {
- return $this->fromJson($attribute) === $this->fromJson($original);
- } elseif ($this->isClassCastable($key) && $original !== null && Str::startsWith($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) {
- if (empty(static::currentEncrypter()->getPreviousKeys())) {
- return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original);
- }
-
- return false;
- } elseif ($this->isClassComparable($key)) {
- return $this->compareClassCastableAttribute($key, $original, $attribute);
- }
-
- return is_numeric($attribute) && is_numeric($original)
- && strcmp((string) $attribute, (string) $original) === 0;
- }
-
- /**
- * Transform a raw model value using mutators, casts, etc.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function transformModelValue($key, $value)
- {
- // If the attribute has a get mutator, we will call that then return what
- // it returns as the value, which is useful for transforming values on
- // retrieval from the model to a form that is more useful for usage.
- if ($this->hasGetMutator($key)) {
- return $this->mutateAttribute($key, $value);
- } elseif ($this->hasAttributeGetMutator($key)) {
- return $this->mutateAttributeMarkedAttribute($key, $value);
- }
-
- // If the attribute exists within the cast array, we will convert it to
- // an appropriate native PHP type dependent upon the associated value
- // given with the key in the pair. Dayle made this comment line up.
- if ($this->hasCast($key)) {
- if (static::preventsAccessingMissingAttributes() &&
- ! array_key_exists($key, $this->attributes) &&
- ($this->isEnumCastable($key) ||
- in_array($this->getCastType($key), static::$primitiveCastTypes))) {
- $this->throwMissingAttributeExceptionIfApplicable($key);
- }
-
- return $this->castAttribute($key, $value);
- }
-
- // If the attribute is listed as a date, we will convert it to a DateTime
- // instance on retrieval, which makes it quite convenient to work with
- // date fields without having to create a mutator for each property.
- if ($value !== null
- && \in_array($key, $this->getDates(), false)) {
- return $this->asDateTime($value);
- }
-
- return $value;
- }
-
- /**
- * Append attributes to query when building a query.
- *
- * @param array|string $attributes
- * @return $this
- */
- public function append($attributes)
- {
- $this->appends = array_values(array_unique(
- array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
- ));
-
- return $this;
- }
-
- /**
- * Get the accessors that are being appended to model arrays.
- *
- * @return array
- */
- public function getAppends()
- {
- return $this->appends;
- }
-
- /**
- * Set the accessors to append to model arrays.
- *
- * @param array $appends
- * @return $this
- */
- public function setAppends(array $appends)
- {
- $this->appends = $appends;
-
- return $this;
- }
-
- /**
- * Merge new appended attributes with existing appended attributes on the model.
- *
- * @param array $appends
- * @return $this
- */
- public function mergeAppends(array $appends)
- {
- $this->appends = array_values(array_unique(array_merge($this->appends, $appends)));
-
- return $this;
- }
-
- /**
- * Return whether the accessor attribute has been appended.
- *
- * @param string $attribute
- * @return bool
- */
- public function hasAppended($attribute)
- {
- return in_array($attribute, $this->getAppends());
- }
-
- /**
- * Remove all appended properties from the model.
- *
- * @return $this
- */
- public function withoutAppends()
- {
- return $this->setAppends([]);
- }
-
- /**
- * Get the mutated attributes for a given instance.
- *
- * @return array
- */
- public function getMutatedAttributes()
- {
- if (! isset(static::$mutatorCache[static::class])) {
- static::cacheMutatedAttributes($this);
- }
-
- return static::$mutatorCache[static::class];
- }
-
- /**
- * Extract and cache all the mutated attributes of a class.
- *
- * @param object|string $classOrInstance
- * @return void
- */
- public static function cacheMutatedAttributes($classOrInstance)
- {
- $reflection = new ReflectionClass($classOrInstance);
-
- $class = $reflection->getName();
-
- static::$getAttributeMutatorCache[$class] = (new Collection($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($classOrInstance)))
- ->mapWithKeys(fn ($match) => [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true])
- ->all();
-
- static::$mutatorCache[$class] = (new Collection(static::getMutatorMethods($class)))
- ->merge($attributeMutatorMethods)
- ->map(fn ($match) => lcfirst(static::$snakeAttributes ? Str::snake($match) : $match))
- ->all();
- }
-
- /**
- * Get all of the attribute mutator methods.
- *
- * @param mixed $class
- * @return array
- */
- protected static function getMutatorMethods($class)
- {
- preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches);
-
- return $matches[1];
- }
-
- /**
- * Get all of the "Attribute" return typed attribute mutator methods.
- *
- * @param mixed $class
- * @return array
- */
- protected static function getAttributeMarkedMutatorMethods($class)
- {
- $instance = is_object($class) ? $class : new $class;
-
- return (new Collection((new ReflectionClass($instance))->getMethods()))->filter(function ($method) use ($instance) {
- $returnType = $method->getReturnType();
-
- if ($returnType instanceof ReflectionNamedType &&
- $returnType->getName() === Attribute::class) {
- if (is_callable($method->invoke($instance)->get)) {
- return true;
- }
- }
-
- return false;
- })->map->name->values()->all();
- }
-}
diff --git a/Eloquent/Concerns/HasEvents.php b/Eloquent/Concerns/HasEvents.php
deleted file mode 100644
index cc0368e67d..0000000000
--- a/Eloquent/Concerns/HasEvents.php
+++ /dev/null
@@ -1,461 +0,0 @@
-
- */
- protected $dispatchesEvents = [];
-
- /**
- * User exposed observable events.
- *
- * These are extra user-defined events observers may subscribe to.
- *
- * @var string[]
- */
- protected $observables = [];
-
- /**
- * Boot the has event trait for a model.
- *
- * @return void
- */
- public static function bootHasEvents()
- {
- static::whenBooted(fn () => static::observe(static::resolveObserveAttributes()));
- }
-
- /**
- * Resolve the observe class names from the attributes.
- *
- * @return array
- */
- public static function resolveObserveAttributes()
- {
- $reflectionClass = new ReflectionClass(static::class);
-
- $isEloquentGrandchild = is_subclass_of(static::class, Model::class)
- && get_parent_class(static::class) !== Model::class;
-
- return (new Collection($reflectionClass->getAttributes(ObservedBy::class)))
- ->map(fn ($attribute) => $attribute->getArguments())
- ->flatten()
- ->when($isEloquentGrandchild, function (Collection $attributes) {
- return (new Collection(get_parent_class(static::class)::resolveObserveAttributes()))
- ->merge($attributes);
- })
- ->all();
- }
-
- /**
- * Register observers with the model.
- *
- * @param object|string[]|string $classes
- * @return void
- *
- * @throws \RuntimeException
- */
- public static function observe($classes)
- {
- $instance = new static;
-
- foreach (Arr::wrap($classes) as $class) {
- $instance->registerObserver($class);
- }
- }
-
- /**
- * Register a single observer with the model.
- *
- * @param object|string $class
- * @return void
- *
- * @throws \RuntimeException
- */
- protected function registerObserver($class)
- {
- $className = $this->resolveObserverClassName($class);
-
- // When registering a model observer, we will spin through the possible events
- // and determine if this observer has that method. If it does, we will hook
- // it into the model's event system, making it convenient to watch these.
- foreach ($this->getObservableEvents() as $event) {
- if (method_exists($class, $event)) {
- static::registerModelEvent($event, $className.'@'.$event);
- }
- }
- }
-
- /**
- * Resolve the observer's class name from an object or string.
- *
- * @param object|string $class
- * @return class-string
- *
- * @throws \InvalidArgumentException
- */
- private function resolveObserverClassName($class)
- {
- if (is_object($class)) {
- return get_class($class);
- }
-
- if (class_exists($class)) {
- return $class;
- }
-
- throw new InvalidArgumentException('Unable to find observer: '.$class);
- }
-
- /**
- * Get the observable event names.
- *
- * @return string[]
- */
- public function getObservableEvents()
- {
- return array_merge(
- [
- 'retrieved', 'creating', 'created', 'updating', 'updated',
- 'saving', 'saved', 'restoring', 'restored', 'replicating',
- 'trashed', 'deleting', 'deleted', 'forceDeleting', 'forceDeleted',
- ],
- $this->observables
- );
- }
-
- /**
- * Set the observable event names.
- *
- * @param string[] $observables
- * @return $this
- */
- public function setObservableEvents(array $observables)
- {
- $this->observables = $observables;
-
- return $this;
- }
-
- /**
- * Add an observable event name.
- *
- * @param string|string[] $observables
- * @return void
- */
- public function addObservableEvents($observables)
- {
- $this->observables = array_unique(array_merge(
- $this->observables, is_array($observables) ? $observables : func_get_args()
- ));
- }
-
- /**
- * Remove an observable event name.
- *
- * @param string|string[] $observables
- * @return void
- */
- public function removeObservableEvents($observables)
- {
- $this->observables = array_diff(
- $this->observables, is_array($observables) ? $observables : func_get_args()
- );
- }
-
- /**
- * Register a model event with the dispatcher.
- *
- * @param string $event
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- protected static function registerModelEvent($event, $callback)
- {
- if (isset(static::$dispatcher)) {
- $name = static::class;
-
- static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
- }
- }
-
- /**
- * Fire the given event for the model.
- *
- * @param string $event
- * @param bool $halt
- * @return mixed
- */
- protected function fireModelEvent($event, $halt = true)
- {
- if (! isset(static::$dispatcher)) {
- return true;
- }
-
- // First, we will get the proper method to call on the event dispatcher, and then we
- // will attempt to fire a custom, object based event for the given event. If that
- // returns a result we can return that result, or we'll call the string events.
- $method = $halt ? 'until' : 'dispatch';
-
- $result = $this->filterModelEventResults(
- $this->fireCustomModelEvent($event, $method)
- );
-
- if ($result === false) {
- return false;
- }
-
- return ! empty($result) ? $result : static::$dispatcher->{$method}(
- "eloquent.{$event}: ".static::class, $this
- );
- }
-
- /**
- * Fire a custom model event for the given event.
- *
- * @param string $event
- * @param 'until'|'dispatch' $method
- * @return array|null|void
- */
- protected function fireCustomModelEvent($event, $method)
- {
- if (! isset($this->dispatchesEvents[$event])) {
- return;
- }
-
- $result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));
-
- if (! is_null($result)) {
- return $result;
- }
- }
-
- /**
- * Filter the model event results.
- *
- * @param mixed $result
- * @return mixed
- */
- protected function filterModelEventResults($result)
- {
- if (is_array($result)) {
- $result = array_filter($result, function ($response) {
- return ! is_null($response);
- });
- }
-
- return $result;
- }
-
- /**
- * Register a retrieved model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function retrieved($callback)
- {
- static::registerModelEvent('retrieved', $callback);
- }
-
- /**
- * Register a saving model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function saving($callback)
- {
- static::registerModelEvent('saving', $callback);
- }
-
- /**
- * Register a saved model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function saved($callback)
- {
- static::registerModelEvent('saved', $callback);
- }
-
- /**
- * Register an updating model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function updating($callback)
- {
- static::registerModelEvent('updating', $callback);
- }
-
- /**
- * Register an updated model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function updated($callback)
- {
- static::registerModelEvent('updated', $callback);
- }
-
- /**
- * Register a creating model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function creating($callback)
- {
- static::registerModelEvent('creating', $callback);
- }
-
- /**
- * Register a created model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function created($callback)
- {
- static::registerModelEvent('created', $callback);
- }
-
- /**
- * Register a replicating model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function replicating($callback)
- {
- static::registerModelEvent('replicating', $callback);
- }
-
- /**
- * Register a deleting model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function deleting($callback)
- {
- static::registerModelEvent('deleting', $callback);
- }
-
- /**
- * Register a deleted model event with the dispatcher.
- *
- * @param \Illuminate\Events\QueuedClosure|callable|array|class-string $callback
- * @return void
- */
- public static function deleted($callback)
- {
- static::registerModelEvent('deleted', $callback);
- }
-
- /**
- * Remove all the event listeners for the model.
- *
- * @return void
- */
- public static function flushEventListeners()
- {
- if (! isset(static::$dispatcher)) {
- return;
- }
-
- $instance = new static;
-
- foreach ($instance->getObservableEvents() as $event) {
- static::$dispatcher->forget("eloquent.{$event}: ".static::class);
- }
-
- foreach ($instance->dispatchesEvents as $event) {
- static::$dispatcher->forget($event);
- }
- }
-
- /**
- * Get the event map for the model.
- *
- * @return array
- */
- public function dispatchesEvents()
- {
- return $this->dispatchesEvents;
- }
-
- /**
- * Get the event dispatcher instance.
- *
- * @return \Illuminate\Contracts\Events\Dispatcher|null
- */
- public static function getEventDispatcher()
- {
- return static::$dispatcher;
- }
-
- /**
- * Set the event dispatcher instance.
- *
- * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
- * @return void
- */
- public static function setEventDispatcher(Dispatcher $dispatcher)
- {
- static::$dispatcher = $dispatcher;
- }
-
- /**
- * Unset the event dispatcher for models.
- *
- * @return void
- */
- public static function unsetEventDispatcher()
- {
- static::$dispatcher = null;
- }
-
- /**
- * Execute a callback without firing any model events for any model type.
- *
- * @param callable $callback
- * @return mixed
- */
- public static function withoutEvents(callable $callback)
- {
- $dispatcher = static::getEventDispatcher();
-
- if ($dispatcher) {
- static::setEventDispatcher(new NullDispatcher($dispatcher));
- }
-
- try {
- return $callback();
- } finally {
- if ($dispatcher) {
- static::setEventDispatcher($dispatcher);
- }
- }
- }
-}
diff --git a/Eloquent/Concerns/HasGlobalScopes.php b/Eloquent/Concerns/HasGlobalScopes.php
deleted file mode 100644
index 635ac8d1fe..0000000000
--- a/Eloquent/Concerns/HasGlobalScopes.php
+++ /dev/null
@@ -1,145 +0,0 @@
-getAttributes(ScopedBy::class, ReflectionAttribute::IS_INSTANCEOF)));
-
- foreach ($reflectionClass->getTraits() as $trait) {
- $attributes->push(...$trait->getAttributes(ScopedBy::class, ReflectionAttribute::IS_INSTANCEOF));
- }
-
- return $attributes->map(fn ($attribute) => $attribute->getArguments())
- ->flatten()
- ->all();
- }
-
- /**
- * Register a new global scope on the model.
- *
- * @param \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder): mixed)|string $scope
- * @param \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder): mixed)|null $implementation
- * @return mixed
- *
- * @throws \InvalidArgumentException
- */
- public static function addGlobalScope($scope, $implementation = null)
- {
- if (is_string($scope) && ($implementation instanceof Closure || $implementation instanceof Scope)) {
- return static::$globalScopes[static::class][$scope] = $implementation;
- } elseif ($scope instanceof Closure) {
- return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope;
- } elseif ($scope instanceof Scope) {
- return static::$globalScopes[static::class][get_class($scope)] = $scope;
- } elseif (is_string($scope) && class_exists($scope) && is_subclass_of($scope, Scope::class)) {
- return static::$globalScopes[static::class][$scope] = new $scope;
- }
-
- throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope or be a class name of a class extending '.Scope::class);
- }
-
- /**
- * Register multiple global scopes on the model.
- *
- * @param array $scopes
- * @return void
- */
- public static function addGlobalScopes(array $scopes)
- {
- foreach ($scopes as $key => $scope) {
- if (is_string($key)) {
- static::addGlobalScope($key, $scope);
- } else {
- static::addGlobalScope($scope);
- }
- }
- }
-
- /**
- * Determine if a model has a global scope.
- *
- * @param \Illuminate\Database\Eloquent\Scope|string $scope
- * @return bool
- */
- public static function hasGlobalScope($scope)
- {
- return ! is_null(static::getGlobalScope($scope));
- }
-
- /**
- * Get a global scope registered with the model.
- *
- * @param \Illuminate\Database\Eloquent\Scope|string $scope
- * @return \Illuminate\Database\Eloquent\Scope|(\Closure(\Illuminate\Database\Eloquent\Builder): mixed)|null
- */
- public static function getGlobalScope($scope)
- {
- if (is_string($scope)) {
- return Arr::get(static::$globalScopes, static::class.'.'.$scope);
- }
-
- return Arr::get(
- static::$globalScopes, static::class.'.'.get_class($scope)
- );
- }
-
- /**
- * Get all of the global scopes that are currently registered.
- *
- * @return array
- */
- public static function getAllGlobalScopes()
- {
- return static::$globalScopes;
- }
-
- /**
- * Set the current global scopes.
- *
- * @param array $scopes
- * @return void
- */
- public static function setAllGlobalScopes($scopes)
- {
- static::$globalScopes = $scopes;
- }
-
- /**
- * Get the global scopes for this class instance.
- *
- * @return array
- */
- public function getGlobalScopes()
- {
- return Arr::get(static::$globalScopes, static::class, []);
- }
-}
diff --git a/Eloquent/Concerns/HasRelationships.php b/Eloquent/Concerns/HasRelationships.php
deleted file mode 100644
index 9380eb10e0..0000000000
--- a/Eloquent/Concerns/HasRelationships.php
+++ /dev/null
@@ -1,1204 +0,0 @@
-touches)) {
- $this->touches = static::resolveClassAttribute(Touches::class, 'relations') ?? [];
- }
- }
-
- /**
- * Get the dynamic relation resolver if defined or inherited, or return null.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $class
- * @param string $key
- * @return Closure|null
- */
- public function relationResolver($class, $key)
- {
- if ($resolver = static::$relationResolvers[$class][$key] ?? null) {
- return $resolver;
- }
-
- if ($parent = get_parent_class($class)) {
- return $this->relationResolver($parent, $key);
- }
-
- return null;
- }
-
- /**
- * Define a dynamic relation resolver.
- *
- * @param string $name
- * @param \Closure $callback
- * @return void
- */
- public static function resolveRelationUsing($name, Closure $callback)
- {
- static::$relationResolvers = array_replace_recursive(
- static::$relationResolvers,
- [static::class => [$name => $callback]]
- );
- }
-
- /**
- * Determine if a relationship autoloader callback has been defined.
- *
- * @return bool
- */
- public function hasRelationAutoloadCallback()
- {
- return ! is_null($this->relationAutoloadCallback);
- }
-
- /**
- * Define an automatic relationship autoloader callback for this model and its relations.
- *
- * @param \Closure $callback
- * @param mixed $context
- * @return $this
- */
- public function autoloadRelationsUsing(Closure $callback, $context = null)
- {
- // Prevent circular relation autoloading...
- if ($context && $this->relationAutoloadContext === $context) {
- return $this;
- }
-
- $this->relationAutoloadCallback = $callback;
- $this->relationAutoloadContext = $context;
-
- foreach ($this->relations as $key => $value) {
- $this->propagateRelationAutoloadCallbackToRelation($key, $value);
- }
-
- return $this;
- }
-
- /**
- * Attempt to autoload the given relationship using the autoload callback.
- *
- * @param string $key
- * @return bool
- */
- protected function attemptToAutoloadRelation($key)
- {
- if (! $this->hasRelationAutoloadCallback()) {
- return false;
- }
-
- $this->invokeRelationAutoloadCallbackFor($key, []);
-
- return $this->relationLoaded($key);
- }
-
- /**
- * Invoke the relationship autoloader callback for the given relationships.
- *
- * @param string $key
- * @param array $tuples
- * @return void
- */
- protected function invokeRelationAutoloadCallbackFor($key, $tuples)
- {
- $tuples = array_merge([[$key, get_class($this)]], $tuples);
-
- call_user_func($this->relationAutoloadCallback, $tuples);
- }
-
- /**
- * Propagate the relationship autoloader callback to the given related models.
- *
- * @param string $key
- * @param mixed $models
- * @return void
- */
- protected function propagateRelationAutoloadCallbackToRelation($key, $models)
- {
- if (! $this->hasRelationAutoloadCallback() || ! $models) {
- return;
- }
-
- if ($models instanceof Model) {
- $models = [$models];
- }
-
- if (! is_iterable($models)) {
- return;
- }
-
- $callback = fn (array $tuples) => $this->invokeRelationAutoloadCallbackFor($key, $tuples);
-
- foreach ($models as $model) {
- $model->autoloadRelationsUsing($callback, $this->relationAutoloadContext);
- }
- }
-
- /**
- * Define a one-to-one relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string|null $foreignKey
- * @param string|null $localKey
- * @return \Illuminate\Database\Eloquent\Relations\HasOne
- */
- public function hasOne($related, $foreignKey = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- $foreignKey = $foreignKey ?: $this->getForeignKey();
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newHasOne($instance->newQuery(), $this, $instance->qualifyColumn($foreignKey), $localKey);
- }
-
- /**
- * Instantiate a new HasOne relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $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.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param class-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.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $farParent
- * @param TIntermediateModel $throughParent
- * @param string $firstKey
- * @param string $secondKey
- * @param string $localKey
- * @param string $secondLocalKey
- * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
- */
- protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
- {
- return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
- }
-
- /**
- * Define a polymorphic one-to-one relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string $name
- * @param string|null $type
- * @param string|null $id
- * @param string|null $localKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphOne
- */
- public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- [$type, $id] = $this->getMorphs($name, $type, $id);
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newMorphOne($instance->newQuery(), $this, $instance->qualifyColumn($type), $instance->qualifyColumn($id), $localKey);
- }
-
- /**
- * Instantiate a new MorphOne relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $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.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string|null $foreignKey
- * @param string|null $ownerKey
- * @param string|null $relation
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
- */
- public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
- {
- // If no relation name was given, we will use this debug backtrace to extract
- // the calling method's name and use that as the relationship name as most
- // of the time this will be what we desire to use for the relationships.
- if (is_null($relation)) {
- $relation = $this->guessBelongsToRelation();
- }
-
- $instance = $this->newRelatedInstance($related);
-
- // If no foreign key was supplied, we can use a backtrace to guess the proper
- // foreign key name by using the name of the relationship function, which
- // when combined with an "_id" should conventionally match the columns.
- if (is_null($foreignKey)) {
- $foreignKey = Str::snake($relation).'_'.$instance->getKeyName();
- }
-
- // Once we have the foreign key names we'll just create a new Eloquent query
- // for the related models and return the relationship instance which will
- // actually be responsible for retrieving and hydrating every relation.
- $ownerKey = $ownerKey ?: $instance->getKeyName();
-
- return $this->newBelongsTo(
- $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
- );
- }
-
- /**
- * Instantiate a new BelongsTo relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $child
- * @param string $foreignKey
- * @param string $ownerKey
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
- */
- protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
- {
- return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation);
- }
-
- /**
- * Define a polymorphic, inverse one-to-one or many relationship.
- *
- * @param string|null $name
- * @param string|null $type
- * @param string|null $id
- * @param string|null $ownerKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this>
- */
- public function morphTo($name = null, $type = null, $id = null, $ownerKey = null)
- {
- // If no name is provided, we will use the backtrace to get the function name
- // since that is most likely the name of the polymorphic interface. We can
- // use that to get both the class and foreign key that will be utilized.
- $name = $name ?: $this->guessBelongsToRelation();
-
- [$type, $id] = $this->getMorphs(
- Str::snake($name), $type, $id
- );
-
- // If the type value is null it is probably safe to assume we're eager loading
- // the relationship. In this case we'll just pass in a dummy query where we
- // need to remove any eager loads that may already be defined on a model.
- return is_null($class = $this->getAttributeFromArray($type)) || $class === ''
- ? $this->morphEagerTo($name, $type, $id, $ownerKey)
- : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey);
- }
-
- /**
- * Define a polymorphic, inverse one-to-one or many relationship.
- *
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string|null $ownerKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this>
- */
- protected function morphEagerTo($name, $type, $id, $ownerKey)
- {
- return $this->newMorphTo(
- $this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name
- );
- }
-
- /**
- * Define a polymorphic, inverse one-to-one or many relationship.
- *
- * @param string $target
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string|null $ownerKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo<\Illuminate\Database\Eloquent\Model, $this>
- */
- protected function morphInstanceTo($target, $name, $type, $id, $ownerKey)
- {
- $instance = $this->newRelatedInstance(
- static::getActualClassNameForMorph($target)
- );
-
- return $this->newMorphTo(
- $instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name
- );
- }
-
- /**
- * Instantiate a new MorphTo relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $parent
- * @param string $foreignKey
- * @param string|null $ownerKey
- * @param string $type
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo
- */
- protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
- {
- return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation);
- }
-
- /**
- * Retrieve the actual class name for a given morph class.
- *
- * @param string $class
- * @return string
- */
- public static function getActualClassNameForMorph($class)
- {
- return Arr::get(Relation::morphMap() ?: [], $class, $class);
- }
-
- /**
- * Guess the "belongs to" relationship name.
- *
- * @return string
- */
- protected function guessBelongsToRelation()
- {
- [, , $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
-
- return $caller['function'];
- }
-
- /**
- * Create a pending has-many-through or has-one-through relationship.
- *
- * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
- *
- * @param string|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $relationship
- * @return (
- * $relationship is string
- * ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship<\Illuminate\Database\Eloquent\Model, $this>
- * : (
- * $relationship is \Illuminate\Database\Eloquent\Relations\HasMany
- * ? \Illuminate\Database\Eloquent\PendingHasThroughRelationship>
- * : \Illuminate\Database\Eloquent\PendingHasThroughRelationship>
- * )
- * )
- */
- public function through($relationship)
- {
- if (is_string($relationship)) {
- $relationship = $this->{$relationship}();
- }
-
- return new PendingHasThroughRelationship($this, $relationship);
- }
-
- /**
- * Define a one-to-many relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string|null $foreignKey
- * @param string|null $localKey
- * @return \Illuminate\Database\Eloquent\Relations\HasMany
- */
- public function hasMany($related, $foreignKey = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- $foreignKey = $foreignKey ?: $this->getForeignKey();
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newHasMany(
- $instance->newQuery(), $this, $instance->qualifyColumn($foreignKey), $localKey
- );
- }
-
- /**
- * Instantiate a new HasMany relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $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.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param class-string $through
- * @param string|null $firstKey
- * @param string|null $secondKey
- * @param string|null $localKey
- * @param string|null $secondLocalKey
- * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
- */
- public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
- {
- $through = $this->newRelatedThroughInstance($through);
-
- $firstKey = $firstKey ?: $this->getForeignKey();
-
- $secondKey = $secondKey ?: $through->getForeignKey();
-
- return $this->newHasManyThrough(
- $this->newRelatedInstance($related)->newQuery(),
- $this,
- $through,
- $firstKey,
- $secondKey,
- $localKey ?: $this->getKeyName(),
- $secondLocalKey ?: $through->getKeyName()
- );
- }
-
- /**
- * Instantiate a new HasManyThrough relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TIntermediateModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $farParent
- * @param TIntermediateModel $throughParent
- * @param string $firstKey
- * @param string $secondKey
- * @param string $localKey
- * @param string $secondLocalKey
- * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
- */
- protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
- {
- return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
- }
-
- /**
- * Define a polymorphic one-to-many relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string $name
- * @param string|null $type
- * @param string|null $id
- * @param string|null $localKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphMany
- */
- public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- // Here we will gather up the morph type and ID for the relationship so that we
- // can properly query the intermediate table of a relation. Finally, we will
- // get the table and create the relationship instances for the developers.
- [$type, $id] = $this->getMorphs($name, $type, $id);
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newMorphMany($instance->newQuery(), $this, $instance->qualifyColumn($type), $instance->qualifyColumn($id), $localKey);
- }
-
- /**
- * Instantiate a new MorphMany relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $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.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string|class-string<\Illuminate\Database\Eloquent\Model>|null $table
- * @param string|null $foreignPivotKey
- * @param string|null $relatedPivotKey
- * @param string|null $parentKey
- * @param string|null $relatedKey
- * @param string|null $relation
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function belongsToMany(
- $related,
- $table = null,
- $foreignPivotKey = null,
- $relatedPivotKey = null,
- $parentKey = null,
- $relatedKey = null,
- $relation = null,
- ) {
- // If no relationship name was passed, we will pull backtraces to get the
- // name of the calling function. We will use that function name as the
- // title of this relation since that is a great convention to apply.
- if (is_null($relation)) {
- $relation = $this->guessBelongsToManyRelation();
- }
-
- // First, we'll need to determine the foreign key and "other key" for the
- // relationship. Once we have determined the keys we'll make the query
- // instances as well as the relationship instances we need for this.
- $instance = $this->newRelatedInstance($related);
-
- $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
-
- $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
-
- // If no table name was provided, we can guess it by concatenating the two
- // models using underscores in alphabetical order. The two model names
- // are transformed to snake case from their default CamelCase also.
- if (is_null($table)) {
- $table = $this->joiningTable($related, $instance);
- }
-
- return $this->newBelongsToMany(
- $instance->newQuery(),
- $this,
- $table,
- $foreignPivotKey,
- $relatedPivotKey,
- $parentKey ?: $this->getKeyName(),
- $relatedKey ?: $instance->getKeyName(),
- $relation,
- );
- }
-
- /**
- * Instantiate a new BelongsToMany relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param TDeclaringModel $parent
- * @param string|class-string<\Illuminate\Database\Eloquent\Model> $table
- * @param string $foreignPivotKey
- * @param string $relatedPivotKey
- * @param string $parentKey
- * @param string $relatedKey
- * @param string|null $relationName
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- protected function newBelongsToMany(
- Builder $query,
- Model $parent,
- $table,
- $foreignPivotKey,
- $relatedPivotKey,
- $parentKey,
- $relatedKey,
- $relationName = null,
- ) {
- return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName);
- }
-
- /**
- * Define a polymorphic many-to-many relationship.
- *
- * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
- *
- * @param class-string $related
- * @param string $name
- * @param string|null $table
- * @param string|null $foreignPivotKey
- * @param string|null $relatedPivotKey
- * @param string|null $parentKey
- * @param string|null $relatedKey
- * @param string|null $relation
- * @param bool $inverse
- * @return \Illuminate\Database\Eloquent\Relations\MorphToMany