Illuminate: ready. (critical)
- DB에서 illuminate를 가져올 수 있도록 설정 - 업그레이듵 불가, RootDB, DB를 직접 수정해야함
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Capsule;
|
||||
|
||||
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\Support\Traits\CapsuleManagerTrait;
|
||||
use PDO;
|
||||
|
||||
class Manager
|
||||
{
|
||||
use CapsuleManagerTrait;
|
||||
|
||||
/**
|
||||
* The database manager instance.
|
||||
*
|
||||
* @var \Illuminate\Database\DatabaseManager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Create a new database capsule manager.
|
||||
*
|
||||
* @param \Illuminate\Container\Container|null $container
|
||||
* @return void
|
||||
*/
|
||||
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.
|
||||
$this->setupDefaultConfiguration();
|
||||
|
||||
$this->setupManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the default database configuration options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setupDefaultConfiguration()
|
||||
{
|
||||
$this->container['config']['database.fetch'] = PDO::FETCH_OBJ;
|
||||
|
||||
$this->container['config']['database.default'] = 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the database manager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setupManager()
|
||||
{
|
||||
$factory = new ConnectionFactory($this->container);
|
||||
|
||||
$this->manager = new DatabaseManager($this->container, $factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection instance from the global manager.
|
||||
*
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public static function connection($connection = null)
|
||||
{
|
||||
return static::$instance->getConnection($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fluent query builder instance.
|
||||
*
|
||||
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
|
||||
* @param string|null $as
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public static function table($table, $as = null, $connection = null)
|
||||
{
|
||||
return static::$instance->connection($connection)->table($table, $as);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a schema builder instance.
|
||||
*
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Schema\Builder
|
||||
*/
|
||||
public static function schema($connection = null)
|
||||
{
|
||||
return static::$instance->connection($connection)->getSchemaBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a registered connection instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function getConnection($name = null)
|
||||
{
|
||||
return $this->manager->connection($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a connection with the manager.
|
||||
*
|
||||
* @param array $config
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function addConnection(array $config, $name = 'default')
|
||||
{
|
||||
$connections = $this->container['config']['database.connections'];
|
||||
|
||||
$connections[$name] = $config;
|
||||
|
||||
$this->container['config']['database.connections'] = $connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap Eloquent so it is ready for usage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function bootEloquent()
|
||||
{
|
||||
Eloquent::setConnectionResolver($this->manager);
|
||||
|
||||
// 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.
|
||||
if ($dispatcher = $this->getEventDispatcher()) {
|
||||
Eloquent::setEventDispatcher($dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fetch mode for the database connections.
|
||||
*
|
||||
* @param int $fetchMode
|
||||
* @return $this
|
||||
*/
|
||||
public function setFetchMode($fetchMode)
|
||||
{
|
||||
$this->container['config']['database.fetch'] = $fetchMode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database manager instance.
|
||||
*
|
||||
* @return \Illuminate\Database\DatabaseManager
|
||||
*/
|
||||
public function getDatabaseManager()
|
||||
{
|
||||
return $this->manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current event dispatcher instance.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Events\Dispatcher|null
|
||||
*/
|
||||
public function getEventDispatcher()
|
||||
{
|
||||
if ($this->container->bound('events')) {
|
||||
return $this->container['events'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event dispatcher instance to be used by connections.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return void
|
||||
*/
|
||||
public function setEventDispatcher(Dispatcher $dispatcher)
|
||||
{
|
||||
$this->container->instance('events', $dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically pass methods to the default connection.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic($method, $parameters)
|
||||
{
|
||||
return static::connection()->$method(...$parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ClassMorphViolationException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* The name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param object $model
|
||||
*/
|
||||
public function __construct($model)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
parent::__construct("No morph map defined for model [{$class}].");
|
||||
|
||||
$this->model = $class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\MultipleRecordsFoundException;
|
||||
use Illuminate\Database\RecordsNotFoundException;
|
||||
use Illuminate\Pagination\Cursor;
|
||||
use Illuminate\Pagination\CursorPaginator;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use Illuminate\Support\Traits\Conditionable;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
trait BuildsQueries
|
||||
{
|
||||
use Conditionable;
|
||||
|
||||
/**
|
||||
* Chunk the results of the query.
|
||||
*
|
||||
* @param int $count
|
||||
* @param callable $callback
|
||||
* @return bool
|
||||
*/
|
||||
public function chunk($count, callable $callback)
|
||||
{
|
||||
$this->enforceOrderBy();
|
||||
|
||||
$page = 1;
|
||||
|
||||
do {
|
||||
// We'll execute the query for the given page and get the results. If there are
|
||||
// no results we can just break and return from here. When there are results
|
||||
// we will call the callback with the current chunk of these results here.
|
||||
$results = $this->forPage($page, $count)->get();
|
||||
|
||||
$countResults = $results->count();
|
||||
|
||||
if ($countResults == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// On each chunk result set, we will pass them to the callback and then let the
|
||||
// developer take care of everything within the callback, which allows us to
|
||||
// keep the memory low for spinning through large result sets for working.
|
||||
if ($callback($results, $page) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($results);
|
||||
|
||||
$page++;
|
||||
} while ($countResults == $count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a map over each item while chunking.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function chunkMap(callable $callback, $count = 1000)
|
||||
{
|
||||
$collection = Collection::make();
|
||||
|
||||
$this->chunk($count, function ($items) use ($collection, $callback) {
|
||||
$items->each(function ($item) use ($collection, $callback) {
|
||||
$collection->push($callback($item));
|
||||
});
|
||||
});
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item while chunking.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @return bool
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function each(callable $callback, $count = 1000)
|
||||
{
|
||||
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 $callback
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return bool
|
||||
*/
|
||||
public function chunkById($count, callable $callback, $column = null, $alias = null)
|
||||
{
|
||||
$column = $column ?? $this->defaultKeyName();
|
||||
|
||||
$alias = $alias ?? $column;
|
||||
|
||||
$lastId = null;
|
||||
|
||||
$page = 1;
|
||||
|
||||
do {
|
||||
$clone = clone $this;
|
||||
|
||||
// We'll execute the query for the given page and get the results. If there are
|
||||
// no results we can just break and return from here. When there are results
|
||||
// we will call the callback with the current chunk of these results here.
|
||||
$results = $clone->forPageAfterId($count, $lastId, $column)->get();
|
||||
|
||||
$countResults = $results->count();
|
||||
|
||||
if ($countResults == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// On each chunk result set, we will pass them to the callback and then let the
|
||||
// developer take care of everything within the callback, which allows us to
|
||||
// keep the memory low for spinning through large result sets for working.
|
||||
if ($callback($results, $page) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastId = $results->last()->{$alias};
|
||||
|
||||
if ($lastId === null) {
|
||||
throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result.");
|
||||
}
|
||||
|
||||
unset($results);
|
||||
|
||||
$page++;
|
||||
} while ($countResults == $count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item while chunking by ID.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return bool
|
||||
*/
|
||||
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
|
||||
{
|
||||
return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
|
||||
foreach ($results as $key => $value) {
|
||||
if ($callback($value, (($page - 1) * $count) + $key) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunks of the given size.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function lazy($chunkSize = 1000)
|
||||
{
|
||||
if ($chunkSize < 1) {
|
||||
throw new InvalidArgumentException('The chunk size should be at least 1');
|
||||
}
|
||||
|
||||
$this->enforceOrderBy();
|
||||
|
||||
return LazyCollection::make(function () use ($chunkSize) {
|
||||
$page = 1;
|
||||
|
||||
while (true) {
|
||||
$results = $this->forPage($page++, $chunkSize)->get();
|
||||
|
||||
foreach ($results as $result) {
|
||||
yield $result;
|
||||
}
|
||||
|
||||
if ($results->count() < $chunkSize) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
|
||||
{
|
||||
if ($chunkSize < 1) {
|
||||
throw new InvalidArgumentException('The chunk size should be at least 1');
|
||||
}
|
||||
|
||||
$column = $column ?? $this->defaultKeyName();
|
||||
|
||||
$alias = $alias ?? $column;
|
||||
|
||||
return LazyCollection::make(function () use ($chunkSize, $column, $alias) {
|
||||
$lastId = null;
|
||||
|
||||
while (true) {
|
||||
$clone = clone $this;
|
||||
|
||||
$results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
|
||||
|
||||
foreach ($results as $result) {
|
||||
yield $result;
|
||||
}
|
||||
|
||||
if ($results->count() < $chunkSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lastId = $results->last()->{$alias};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|object|static|null
|
||||
*/
|
||||
public function first($columns = ['*'])
|
||||
{
|
||||
return $this->take(1)->get($columns)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result if it's the sole matching record.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|object|static|null
|
||||
*
|
||||
* @throws \Illuminate\Database\RecordsNotFoundException
|
||||
* @throws \Illuminate\Database\MultipleRecordsFoundException
|
||||
*/
|
||||
public function sole($columns = ['*'])
|
||||
{
|
||||
$result = $this->take(2)->get($columns);
|
||||
|
||||
if ($result->isEmpty()) {
|
||||
throw new RecordsNotFoundException;
|
||||
}
|
||||
|
||||
if ($result->count() > 1) {
|
||||
throw new MultipleRecordsFoundException;
|
||||
}
|
||||
|
||||
return $result->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query using a cursor paginator.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @param array $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)) {
|
||||
$addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) {
|
||||
if (! is_null($previousColumn)) {
|
||||
$builder->where(
|
||||
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
|
||||
'=',
|
||||
$cursor->parameter($previousColumn)
|
||||
);
|
||||
}
|
||||
|
||||
$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i) {
|
||||
['column' => $column, 'direction' => $direction] = $orders[$i];
|
||||
|
||||
$builder->where(
|
||||
$this->getOriginalColumnNameForCursorPagination($this, $column),
|
||||
$direction === 'asc' ? '>' : '<',
|
||||
$cursor->parameter($column)
|
||||
);
|
||||
|
||||
if ($i < $orders->count() - 1) {
|
||||
$builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
|
||||
$addCursorConditions($builder, $column, $i + 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$addCursorConditions($this, null, 0);
|
||||
}
|
||||
|
||||
$this->limit($perPage + 1);
|
||||
|
||||
return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'cursorName' => $cursorName,
|
||||
'parameters' => $orders->pluck('column')->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original column name of the given column, without any aliasing.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
|
||||
* @param string $parameter
|
||||
* @return string
|
||||
*/
|
||||
protected function getOriginalColumnNameForCursorPagination($builder, string $parameter)
|
||||
{
|
||||
$columns = $builder instanceof Builder ? $builder->getQuery()->columns : $builder->columns;
|
||||
|
||||
if (! is_null($columns)) {
|
||||
foreach ($columns as $column) {
|
||||
if (($position = stripos($column, ' as ')) !== false) {
|
||||
$as = substr($column, $position, 4);
|
||||
|
||||
[$original, $alias] = explode($as, $column);
|
||||
|
||||
if ($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.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this|mixed
|
||||
*/
|
||||
public function tap($callback)
|
||||
{
|
||||
return $this->when(true, $callback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait ExplainsQueries
|
||||
{
|
||||
/**
|
||||
* Explains the query.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function explain()
|
||||
{
|
||||
$sql = $this->toSql();
|
||||
|
||||
$bindings = $this->getBindings();
|
||||
|
||||
$explanation = $this->getConnection()->select('EXPLAIN '.$sql, $bindings);
|
||||
|
||||
return new Collection($explanation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Concerns;
|
||||
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
trait ManagesTransactions
|
||||
{
|
||||
/**
|
||||
* Execute a Closure within a transaction.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @param int $attempts
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function transaction(Closure $callback, $attempts = 1)
|
||||
{
|
||||
for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
|
||||
$this->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 exceptions.
|
||||
catch (Throwable $e) {
|
||||
$this->handleTransactionException(
|
||||
$e, $currentAttempt, $attempts
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->transactions == 1) {
|
||||
$this->getPdo()->commit();
|
||||
}
|
||||
|
||||
$this->transactions = max(0, $this->transactions - 1);
|
||||
|
||||
if ($this->transactions == 0) {
|
||||
optional($this->transactionsManager)->commit($this->getName());
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->handleCommitTransactionException(
|
||||
$e, $currentAttempt, $attempts
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->fireConnectionEvent('committed');
|
||||
|
||||
return $callbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception encountered when running a transacted statement.
|
||||
*
|
||||
* @param \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--;
|
||||
|
||||
optional($this->transactionsManager)->rollback(
|
||||
$this->getName(), $this->transactions
|
||||
);
|
||||
|
||||
throw $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()
|
||||
{
|
||||
$this->createTransaction();
|
||||
|
||||
$this->transactions++;
|
||||
|
||||
optional($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->getPdo()->beginTransaction();
|
||||
} 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->getPdo()->beginTransaction();
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the active database transaction.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
if ($this->transactions == 1) {
|
||||
$this->getPdo()->commit();
|
||||
}
|
||||
|
||||
$this->transactions = max(0, $this->transactions - 1);
|
||||
|
||||
if ($this->transactions == 0) {
|
||||
optional($this->transactionsManager)->commit($this->getName());
|
||||
}
|
||||
|
||||
$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;
|
||||
|
||||
optional($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) {
|
||||
$this->getPdo()->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;
|
||||
|
||||
optional($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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Illuminate\Support\ConfigurationUrlParser as BaseConfigurationUrlParser;
|
||||
|
||||
class ConfigurationUrlParser extends BaseConfigurationUrlParser
|
||||
{
|
||||
//
|
||||
}
|
||||
+1409
@@ -0,0 +1,1409 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Closure;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Connection as DoctrineConnection;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
use Illuminate\Database\Events\StatementPrepared;
|
||||
use Illuminate\Database\Events\TransactionBeginning;
|
||||
use Illuminate\Database\Events\TransactionCommitted;
|
||||
use Illuminate\Database\Events\TransactionRolledBack;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Database\Query\Grammars\Grammar as QueryGrammar;
|
||||
use Illuminate\Database\Query\Processors\Processor;
|
||||
use Illuminate\Database\Schema\Builder as SchemaBuilder;
|
||||
use Illuminate\Support\Arr;
|
||||
use LogicException;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
|
||||
class Connection implements ConnectionInterface
|
||||
{
|
||||
use DetectsConcurrencyErrors,
|
||||
DetectsLostConnections,
|
||||
Concerns\ManagesTransactions;
|
||||
|
||||
/**
|
||||
* The active PDO connection.
|
||||
*
|
||||
* @var \PDO|\Closure
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* The active PDO connection used for reads.
|
||||
*
|
||||
* @var \PDO|\Closure
|
||||
*/
|
||||
protected $readPdo;
|
||||
|
||||
/**
|
||||
* The name of the connected database.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The type of the connection.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The table prefix for the connection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tablePrefix = '';
|
||||
|
||||
/**
|
||||
* The database connection configuration options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* The reconnector instance for the connection.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $reconnector;
|
||||
|
||||
/**
|
||||
* The query grammar implementation.
|
||||
*
|
||||
* @var \Illuminate\Database\Query\Grammars\Grammar
|
||||
*/
|
||||
protected $queryGrammar;
|
||||
|
||||
/**
|
||||
* The schema grammar implementation.
|
||||
*
|
||||
* @var \Illuminate\Database\Schema\Grammars\Grammar
|
||||
*/
|
||||
protected $schemaGrammar;
|
||||
|
||||
/**
|
||||
* The query post processor implementation.
|
||||
*
|
||||
* @var \Illuminate\Database\Query\Processors\Processor
|
||||
*/
|
||||
protected $postProcessor;
|
||||
|
||||
/**
|
||||
* The event dispatcher instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* The default fetch mode of the connection.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $fetchMode = PDO::FETCH_OBJ;
|
||||
|
||||
/**
|
||||
* The number of active transactions.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $transactions = 0;
|
||||
|
||||
/**
|
||||
* The transaction manager instance.
|
||||
*
|
||||
* @var \Illuminate\Database\DatabaseTransactionsManager
|
||||
*/
|
||||
protected $transactionsManager;
|
||||
|
||||
/**
|
||||
* Indicates if changes have been made to the database.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $recordsModified = false;
|
||||
|
||||
/**
|
||||
* Indicates if the connection should use the "write" PDO connection.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $readOnWriteConnection = false;
|
||||
|
||||
/**
|
||||
* All of the queries run against the connection.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queryLog = [];
|
||||
|
||||
/**
|
||||
* Indicates whether queries are being logged.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $loggingQueries = false;
|
||||
|
||||
/**
|
||||
* Indicates if the connection is in a "dry run".
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $pretending = false;
|
||||
|
||||
/**
|
||||
* The instance of Doctrine connection.
|
||||
*
|
||||
* @var \Doctrine\DBAL\Connection
|
||||
*/
|
||||
protected $doctrineConnection;
|
||||
|
||||
/**
|
||||
* The connection resolvers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $resolvers = [];
|
||||
|
||||
/**
|
||||
* Create a new database connection instance.
|
||||
*
|
||||
* @param \PDO|\Closure $pdo
|
||||
* @param string $database
|
||||
* @param string $tablePrefix
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
|
||||
// First we will setup the default properties. We keep track of the DB
|
||||
// name we are connected to since it is needed when some reflective
|
||||
// type commands are run such as checking whether a table exists.
|
||||
$this->database = $database;
|
||||
|
||||
$this->tablePrefix = $tablePrefix;
|
||||
|
||||
$this->config = $config;
|
||||
|
||||
// We need to initialize a query grammar and the query post processors
|
||||
// which are both very important parts of the database abstractions
|
||||
// so we initialize these to their default values while starting.
|
||||
$this->useDefaultQueryGrammar();
|
||||
|
||||
$this->useDefaultPostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query grammar to the default implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function useDefaultQueryGrammar()
|
||||
{
|
||||
$this->queryGrammar = $this->getDefaultQueryGrammar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default query grammar instance.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Grammars\Grammar
|
||||
*/
|
||||
protected function getDefaultQueryGrammar()
|
||||
{
|
||||
return new QueryGrammar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the schema grammar to the default implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function useDefaultSchemaGrammar()
|
||||
{
|
||||
$this->schemaGrammar = $this->getDefaultSchemaGrammar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default schema grammar instance.
|
||||
*
|
||||
* @return \Illuminate\Database\Schema\Grammars\Grammar
|
||||
*/
|
||||
protected function getDefaultSchemaGrammar()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query post processor to the default implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function useDefaultPostProcessor()
|
||||
{
|
||||
$this->postProcessor = $this->getDefaultPostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default post processor instance.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Processors\Processor
|
||||
*/
|
||||
protected function getDefaultPostProcessor()
|
||||
{
|
||||
return new Processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a schema builder instance for the connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Schema\Builder
|
||||
*/
|
||||
public function getSchemaBuilder()
|
||||
{
|
||||
if (is_null($this->schemaGrammar)) {
|
||||
$this->useDefaultSchemaGrammar();
|
||||
}
|
||||
|
||||
return new SchemaBuilder($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a fluent query against a database table.
|
||||
*
|
||||
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
|
||||
* @param string|null $as
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function table($table, $as = null)
|
||||
{
|
||||
return $this->query()->from($table, $as);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query builder instance.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function query()
|
||||
{
|
||||
return new QueryBuilder(
|
||||
$this, $this->getQueryGrammar(), $this->getPostProcessor()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a select statement and return a single result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return mixed
|
||||
*/
|
||||
public function selectOne($query, $bindings = [], $useReadPdo = true)
|
||||
{
|
||||
$records = $this->select($query, $bindings, $useReadPdo);
|
||||
|
||||
return array_shift($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a select statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return array
|
||||
*/
|
||||
public function selectFromWriteConnection($query, $bindings = [])
|
||||
{
|
||||
return $this->select($query, $bindings, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a select statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return array
|
||||
*/
|
||||
public function select($query, $bindings = [], $useReadPdo = true)
|
||||
{
|
||||
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
|
||||
if ($this->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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a select statement against the database and returns a generator.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return \Generator
|
||||
*/
|
||||
public function cursor($query, $bindings = [], $useReadPdo = true)
|
||||
{
|
||||
$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)
|
||||
);
|
||||
|
||||
// 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()) {
|
||||
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.
|
||||
*
|
||||
* @param bool $useReadPdo
|
||||
* @return \PDO
|
||||
*/
|
||||
protected function getPdoForSelect($useReadPdo = true)
|
||||
{
|
||||
return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an insert statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function insert($query, $bindings = [])
|
||||
{
|
||||
return $this->statement($query, $bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function update($query, $bindings = [])
|
||||
{
|
||||
return $this->affectingStatement($query, $bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a delete statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function delete($query, $bindings = [])
|
||||
{
|
||||
return $this->affectingStatement($query, $bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an SQL statement and return the boolean result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function statement($query, $bindings = [])
|
||||
{
|
||||
return $this->run($query, $bindings, function ($query, $bindings) {
|
||||
if ($this->pretending()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$statement = $this->getPdo()->prepare($query);
|
||||
|
||||
$this->bindValues($statement, $this->prepareBindings($bindings));
|
||||
|
||||
$this->recordsHaveBeenModified();
|
||||
|
||||
return $statement->execute();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an SQL statement and get the number of rows affected.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function affectingStatement($query, $bindings = [])
|
||||
{
|
||||
return $this->run($query, $bindings, function ($query, $bindings) {
|
||||
if ($this->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);
|
||||
|
||||
$this->bindValues($statement, $this->prepareBindings($bindings));
|
||||
|
||||
$statement->execute();
|
||||
|
||||
$this->recordsHaveBeenModified(
|
||||
($count = $statement->rowCount()) > 0
|
||||
);
|
||||
|
||||
return $count;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a raw, unprepared query against the PDO connection.
|
||||
*
|
||||
* @param string $query
|
||||
* @return bool
|
||||
*/
|
||||
public function unprepared($query)
|
||||
{
|
||||
return $this->run($query, [], function ($query) {
|
||||
if ($this->pretending()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->recordsHaveBeenModified(
|
||||
$change = $this->getPdo()->exec($query) !== false
|
||||
);
|
||||
|
||||
return $change;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given callback in "dry run" mode.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return array
|
||||
*/
|
||||
public function pretend(Closure $callback)
|
||||
{
|
||||
return $this->withFreshQueryLog(function () use ($callback) {
|
||||
$this->pretending = true;
|
||||
|
||||
// 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;
|
||||
|
||||
return $this->queryLog;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given callback in "dry run" mode.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return array
|
||||
*/
|
||||
protected function withFreshQueryLog($callback)
|
||||
{
|
||||
$loggingQueries = $this->loggingQueries;
|
||||
|
||||
// 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();
|
||||
|
||||
$this->queryLog = [];
|
||||
|
||||
// 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();
|
||||
|
||||
$this->loggingQueries = $loggingQueries;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind values to their parameters in the given statement.
|
||||
*
|
||||
* @param \PDOStatement $statement
|
||||
* @param array $bindings
|
||||
* @return void
|
||||
*/
|
||||
public function bindValues($statement, $bindings)
|
||||
{
|
||||
foreach ($bindings as $key => $value) {
|
||||
$statement->bindValue(
|
||||
is_string($key) ? $key : $key + 1,
|
||||
$value,
|
||||
is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the query bindings for execution.
|
||||
*
|
||||
* @param array $bindings
|
||||
* @return array
|
||||
*/
|
||||
public function prepareBindings(array $bindings)
|
||||
{
|
||||
$grammar = $this->getQueryGrammar();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a SQL statement and log its execution context.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Database\QueryException
|
||||
*/
|
||||
protected function run($query, $bindings, Closure $callback)
|
||||
{
|
||||
$this->reconnectIfMissingConnection();
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
// Here we will run this query. If an exception occurs we'll determine if it was
|
||||
// caused by a connection that has been lost. If that is the cause, we'll try
|
||||
// to re-establish connection and re-run the query with a fresh connection.
|
||||
try {
|
||||
$result = $this->runQueryCallback($query, $bindings, $callback);
|
||||
} catch (QueryException $e) {
|
||||
$result = $this->handleQueryException(
|
||||
$e, $query, $bindings, $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)
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a SQL statement.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Database\QueryException
|
||||
*/
|
||||
protected function runQueryCallback($query, $bindings, Closure $callback)
|
||||
{
|
||||
// To execute the statement, we'll simply call the callback, which will actually
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
throw new QueryException(
|
||||
$query, $this->prepareBindings($bindings), $e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->event(new QueryExecuted($query, $bindings, $time, $this));
|
||||
|
||||
if ($this->loggingQueries) {
|
||||
$this->queryLog[] = compact('query', 'bindings', 'time');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elapsed time since a given starting point.
|
||||
*
|
||||
* @param int $start
|
||||
* @return float
|
||||
*/
|
||||
protected function getElapsedTime($start)
|
||||
{
|
||||
return round((microtime(true) - $start) * 1000, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a query exception that occurred during query execution.
|
||||
*
|
||||
* @param \Illuminate\Database\QueryException $e
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Database\QueryException
|
||||
*/
|
||||
protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback)
|
||||
{
|
||||
if ($this->causedByLostConnection($e->getPrevious())) {
|
||||
$this->reconnect();
|
||||
|
||||
return $this->runQueryCallback($query, $bindings, $callback);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to the database.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
if (is_callable($this->reconnector)) {
|
||||
$this->doctrineConnection = null;
|
||||
|
||||
return call_user_func($this->reconnector, $this);
|
||||
}
|
||||
|
||||
throw new LogicException('Lost connection and no reconnector available.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to the database if a PDO connection is missing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function reconnectIfMissingConnection()
|
||||
{
|
||||
if (is_null($this->pdo)) {
|
||||
$this->reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the underlying PDO connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->setPdo(null)->setReadPdo(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a database query listener with the connection.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public function listen(Closure $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)
|
||||
{
|
||||
if (! isset($this->events)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($event) {
|
||||
case 'beganTransaction':
|
||||
return $this->events->dispatch(new TransactionBeginning($this));
|
||||
case 'committed':
|
||||
return $this->events->dispatch(new TransactionCommitted($this));
|
||||
case 'rollingBack':
|
||||
return $this->events->dispatch(new TransactionRolledBack($this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the given event if possible.
|
||||
*
|
||||
* @param mixed $event
|
||||
* @return void
|
||||
*/
|
||||
protected function event($event)
|
||||
{
|
||||
if (isset($this->events)) {
|
||||
$this->events->dispatch($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new raw query expression.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Query\Expression
|
||||
*/
|
||||
public function raw($value)
|
||||
{
|
||||
return new Expression($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the database connection has modified any database records.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasModifiedRecords()
|
||||
{
|
||||
return $this->recordsModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if any records have been modified.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return void
|
||||
*/
|
||||
public function recordsHaveBeenModified($value = true)
|
||||
{
|
||||
if (! $this->recordsModified) {
|
||||
$this->recordsModified = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the record modification state.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setRecordModificationState(bool $value)
|
||||
{
|
||||
$this->recordsModified = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the record modification state.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forgetRecordModificationState()
|
||||
{
|
||||
$this->recordsModified = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the connection should use the write PDO connection for reads.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function useWriteConnectionWhenReading($value = true)
|
||||
{
|
||||
$this->readOnWriteConnection = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Doctrine available?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDoctrineAvailable()
|
||||
{
|
||||
return class_exists('Doctrine\DBAL\Connection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Doctrine Schema Column instance.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
* @return \Doctrine\DBAL\Schema\Column
|
||||
*/
|
||||
public function getDoctrineColumn($table, $column)
|
||||
{
|
||||
$schema = $this->getDoctrineSchemaManager();
|
||||
|
||||
return $schema->listTableDetails($table)->getColumn($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Doctrine DBAL schema manager for the connection.
|
||||
*
|
||||
* @return \Doctrine\DBAL\Schema\AbstractSchemaManager
|
||||
*/
|
||||
public function getDoctrineSchemaManager()
|
||||
{
|
||||
$connection = $this->getDoctrineConnection();
|
||||
|
||||
// Doctrine v2 expects one parameter while v3 expects two. 2nd will be ignored on v2...
|
||||
return $this->getDoctrineDriver()->getSchemaManager(
|
||||
$connection,
|
||||
$connection->getDatabasePlatform()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Doctrine DBAL database connection instance.
|
||||
*
|
||||
* @return \Doctrine\DBAL\Connection
|
||||
*/
|
||||
public function getDoctrineConnection()
|
||||
{
|
||||
if (is_null($this->doctrineConnection)) {
|
||||
$driver = $this->getDoctrineDriver();
|
||||
|
||||
$this->doctrineConnection = new DoctrineConnection(array_filter([
|
||||
'pdo' => $this->getPdo(),
|
||||
'dbname' => $this->getDatabaseName(),
|
||||
'driver' => method_exists($driver, 'getName') ? $driver->getName() : null,
|
||||
'serverVersion' => $this->getConfig('server_version'),
|
||||
]), $driver);
|
||||
}
|
||||
|
||||
return $this->doctrineConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current PDO connection.
|
||||
*
|
||||
* @return \PDO
|
||||
*/
|
||||
public function getPdo()
|
||||
{
|
||||
if ($this->pdo instanceof Closure) {
|
||||
return $this->pdo = call_user_func($this->pdo);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @return \PDO
|
||||
*/
|
||||
public function getReadPdo()
|
||||
{
|
||||
if ($this->transactions > 0) {
|
||||
return $this->getPdo();
|
||||
}
|
||||
|
||||
if ($this->readOnWriteConnection ||
|
||||
($this->recordsModified && $this->getConfig('sticky'))) {
|
||||
return $this->getPdo();
|
||||
}
|
||||
|
||||
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
|
||||
* @return $this
|
||||
*/
|
||||
public function setPdo($pdo)
|
||||
{
|
||||
$this->transactions = 0;
|
||||
|
||||
$this->pdo = $pdo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PDO connection used for reading.
|
||||
*
|
||||
* @param \PDO|\Closure|null $pdo
|
||||
* @return $this
|
||||
*/
|
||||
public function setReadPdo($pdo)
|
||||
{
|
||||
$this->readPdo = $pdo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reconnect instance on the connection.
|
||||
*
|
||||
* @param callable $reconnector
|
||||
* @return $this
|
||||
*/
|
||||
public function setReconnector(callable $reconnector)
|
||||
{
|
||||
$this->reconnector = $reconnector;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection name.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->getConfig('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection full name.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNameWithReadWriteType()
|
||||
{
|
||||
return $this->getName().($this->readWriteType ? '::'.$this->readWriteType : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option from the configuration options.
|
||||
*
|
||||
* @param string|null $option
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig($option = null)
|
||||
{
|
||||
return Arr::get($this->config, $option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDO driver name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDriverName()
|
||||
{
|
||||
return $this->getConfig('driver');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query grammar used by the connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Grammars\Grammar
|
||||
*/
|
||||
public function getQueryGrammar()
|
||||
{
|
||||
return $this->queryGrammar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query grammar used by the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Grammars\Grammar $grammar
|
||||
* @return $this
|
||||
*/
|
||||
public function setQueryGrammar(Query\Grammars\Grammar $grammar)
|
||||
{
|
||||
$this->queryGrammar = $grammar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema grammar used by the connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Schema\Grammars\Grammar
|
||||
*/
|
||||
public function getSchemaGrammar()
|
||||
{
|
||||
return $this->schemaGrammar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the schema grammar used by the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
|
||||
* @return $this
|
||||
*/
|
||||
public function setSchemaGrammar(Schema\Grammars\Grammar $grammar)
|
||||
{
|
||||
$this->schemaGrammar = $grammar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query post processor used by the connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Processors\Processor
|
||||
*/
|
||||
public function getPostProcessor()
|
||||
{
|
||||
return $this->postProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query post processor used by the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Processors\Processor $processor
|
||||
* @return $this
|
||||
*/
|
||||
public function setPostProcessor(Processor $processor)
|
||||
{
|
||||
$this->postProcessor = $processor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event dispatcher used by the connection.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
public function getEventDispatcher()
|
||||
{
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event dispatcher instance on the connection.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return $this
|
||||
*/
|
||||
public function setEventDispatcher(Dispatcher $events)
|
||||
{
|
||||
$this->events = $events;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the event dispatcher for this connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetEventDispatcher()
|
||||
{
|
||||
$this->events = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction manager instance on the connection.
|
||||
*
|
||||
* @param \Illuminate\Database\DatabaseTransactionsManager $manager
|
||||
* @return $this
|
||||
*/
|
||||
public function setTransactionManager($manager)
|
||||
{
|
||||
$this->transactionsManager = $manager;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the transaction manager for this connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetTransactionManager()
|
||||
{
|
||||
$this->transactionsManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the connection is in a "dry run".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function pretending()
|
||||
{
|
||||
return $this->pretending === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection query log.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryLog()
|
||||
{
|
||||
return $this->queryLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the query log.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flushQueryLog()
|
||||
{
|
||||
$this->queryLog = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the query log on the connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enableQueryLog()
|
||||
{
|
||||
$this->loggingQueries = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the query log on the connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disableQueryLog()
|
||||
{
|
||||
$this->loggingQueries = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether we're logging queries.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function logging()
|
||||
{
|
||||
return $this->loggingQueries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the connected database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatabaseName()
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the connected database.
|
||||
*
|
||||
* @param string $database
|
||||
* @return $this
|
||||
*/
|
||||
public function setDatabaseName($database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read / write type of the connection.
|
||||
*
|
||||
* @param string|null $readWriteType
|
||||
* @return $this
|
||||
*/
|
||||
public function setReadWriteType($readWriteType)
|
||||
{
|
||||
$this->readWriteType = $readWriteType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table prefix for the connection.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTablePrefix()
|
||||
{
|
||||
return $this->tablePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table prefix in use by the connection.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @return $this
|
||||
*/
|
||||
public function setTablePrefix($prefix)
|
||||
{
|
||||
$this->tablePrefix = $prefix;
|
||||
|
||||
$this->getQueryGrammar()->setTablePrefix($prefix);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table prefix and return the grammar.
|
||||
*
|
||||
* @param \Illuminate\Database\Grammar $grammar
|
||||
* @return \Illuminate\Database\Grammar
|
||||
*/
|
||||
public function withTablePrefix(Grammar $grammar)
|
||||
{
|
||||
$grammar->setTablePrefix($this->tablePrefix);
|
||||
|
||||
return $grammar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mixed
|
||||
*/
|
||||
public static function getResolver($driver)
|
||||
{
|
||||
return static::$resolvers[$driver] ?? null;
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Closure;
|
||||
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Begin a fluent query against a database table.
|
||||
*
|
||||
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
|
||||
* @param string|null $as
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function table($table, $as = null);
|
||||
|
||||
/**
|
||||
* Get a new raw query expression.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Query\Expression
|
||||
*/
|
||||
public function raw($value);
|
||||
|
||||
/**
|
||||
* Run a select statement and return a single result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return mixed
|
||||
*/
|
||||
public function selectOne($query, $bindings = [], $useReadPdo = true);
|
||||
|
||||
/**
|
||||
* Run a select statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return array
|
||||
*/
|
||||
public function select($query, $bindings = [], $useReadPdo = true);
|
||||
|
||||
/**
|
||||
* Run a select statement against the database and returns a generator.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param bool $useReadPdo
|
||||
* @return \Generator
|
||||
*/
|
||||
public function cursor($query, $bindings = [], $useReadPdo = true);
|
||||
|
||||
/**
|
||||
* Run an insert statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function insert($query, $bindings = []);
|
||||
|
||||
/**
|
||||
* Run an update statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function update($query, $bindings = []);
|
||||
|
||||
/**
|
||||
* Run a delete statement against the database.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function delete($query, $bindings = []);
|
||||
|
||||
/**
|
||||
* Execute an SQL statement and return the boolean result.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return bool
|
||||
*/
|
||||
public function statement($query, $bindings = []);
|
||||
|
||||
/**
|
||||
* Run an SQL statement and get the number of rows affected.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return int
|
||||
*/
|
||||
public function affectingStatement($query, $bindings = []);
|
||||
|
||||
/**
|
||||
* Run a raw, unprepared query against the PDO connection.
|
||||
*
|
||||
* @param string $query
|
||||
* @return bool
|
||||
*/
|
||||
public function unprepared($query);
|
||||
|
||||
/**
|
||||
* Prepare the query bindings for execution.
|
||||
*
|
||||
* @param array $bindings
|
||||
* @return array
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Start a new database transaction.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function beginTransaction();
|
||||
|
||||
/**
|
||||
* Commit the active database transaction.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function commit();
|
||||
|
||||
/**
|
||||
* Rollback the active database transaction.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollBack();
|
||||
|
||||
/**
|
||||
* Get the number of active transactions.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function transactionLevel();
|
||||
|
||||
/**
|
||||
* Execute the given callback in "dry run" mode.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return array
|
||||
*/
|
||||
public function pretend(Closure $callback);
|
||||
|
||||
/**
|
||||
* Get the name of the connected database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatabaseName();
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
class ConnectionResolver implements ConnectionResolverInterface
|
||||
{
|
||||
/**
|
||||
* All of the registered connections.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $connections = [];
|
||||
|
||||
/**
|
||||
* The default connection name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $default;
|
||||
|
||||
/**
|
||||
* Create a new connection resolver instance.
|
||||
*
|
||||
* @param array $connections
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $connections = [])
|
||||
{
|
||||
foreach ($connections as $name => $connection) {
|
||||
$this->addConnection($name, $connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database connection instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\ConnectionInterface
|
||||
*/
|
||||
public function connection($name = null)
|
||||
{
|
||||
if (is_null($name)) {
|
||||
$name = $this->getDefaultConnection();
|
||||
}
|
||||
|
||||
return $this->connections[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a connection to the resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @return void
|
||||
*/
|
||||
public function addConnection($name, ConnectionInterface $connection)
|
||||
{
|
||||
$this->connections[$name] = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a connection has been registered.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasConnection($name)
|
||||
{
|
||||
return isset($this->connections[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default connection name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultConnection()
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default connection name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultConnection($name)
|
||||
{
|
||||
$this->default = $name;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
interface ConnectionResolverInterface
|
||||
{
|
||||
/**
|
||||
* Get a database connection instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\ConnectionInterface
|
||||
*/
|
||||
public function connection($name = null);
|
||||
|
||||
/**
|
||||
* Get the default connection name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultConnection();
|
||||
|
||||
/**
|
||||
* Set the default connection name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultConnection($name);
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\MySqlConnection;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use PDOException;
|
||||
|
||||
class ConnectionFactory
|
||||
{
|
||||
/**
|
||||
* The IoC container instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Container\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Create a new connection factory instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Container\Container $container
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a PDO connection based on the configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function make(array $config, $name = null)
|
||||
{
|
||||
$config = $this->parseConfig($config, $name);
|
||||
|
||||
if (isset($config['read'])) {
|
||||
return $this->createReadWriteConnection($config);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
protected function createSingleConnection(array $config)
|
||||
{
|
||||
$pdo = $this->createPdoResolver($config);
|
||||
|
||||
return $this->createConnection(
|
||||
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a read / write database connection instance.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
protected function createReadWriteConnection(array $config)
|
||||
{
|
||||
$connection = $this->createSingleConnection($this->getWriteConfig($config));
|
||||
|
||||
return $connection->setReadPdo($this->createReadPdo($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PDO instance for reading.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function createReadPdo(array $config)
|
||||
{
|
||||
return $this->createPdoResolver($this->getReadConfig($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the read configuration for a read / write connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected function getReadConfig(array $config)
|
||||
{
|
||||
return $this->mergeReadWriteConfig(
|
||||
$config, $this->getReadWriteConfig($config, 'read')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the write configuration for a read / write connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected function getWriteConfig(array $config)
|
||||
{
|
||||
return $this->mergeReadWriteConfig(
|
||||
$config, $this->getReadWriteConfig($config, 'write')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a read / write level configuration.
|
||||
*
|
||||
* @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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a configuration for a read / write connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @param array $merge
|
||||
* @return array
|
||||
*/
|
||||
protected function mergeReadWriteConfig(array $config, array $merge)
|
||||
{
|
||||
return Arr::except(array_merge($config, $merge), ['read', 'write']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($hosts = $this->parseHosts($config)) as $key => $host) {
|
||||
$config['host'] = $host;
|
||||
|
||||
try {
|
||||
return $this->createConnector($config)->connect($config);
|
||||
} catch (PDOException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the hosts configuration item into an array.
|
||||
*
|
||||
* @param array $config
|
||||
* @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)
|
||||
{
|
||||
return function () use ($config) {
|
||||
return $this->createConnector($config)->connect($config);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connector instance based on the configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \Illuminate\Database\Connectors\ConnectorInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function createConnector(array $config)
|
||||
{
|
||||
if (! isset($config['driver'])) {
|
||||
throw new InvalidArgumentException('A driver must be specified.');
|
||||
}
|
||||
|
||||
if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
|
||||
return $this->container->make($key);
|
||||
}
|
||||
|
||||
switch ($config['driver']) {
|
||||
case 'mysql':
|
||||
return new MySqlConnector;
|
||||
case 'pgsql':
|
||||
return new PostgresConnector;
|
||||
case 'sqlite':
|
||||
return new SQLiteConnector;
|
||||
case 'sqlsrv':
|
||||
return new SqlServerConnector;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connection instance.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
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}].");
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use Doctrine\DBAL\Driver\PDOConnection;
|
||||
use Exception;
|
||||
use Illuminate\Database\DetectsLostConnections;
|
||||
use PDO;
|
||||
use Throwable;
|
||||
|
||||
class Connector
|
||||
{
|
||||
use DetectsLostConnections;
|
||||
|
||||
/**
|
||||
* The default PDO connection options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Create a new PDO connection.
|
||||
*
|
||||
* @param string $dsn
|
||||
* @param array $config
|
||||
* @param array $options
|
||||
* @return \PDO
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createConnection($dsn, array $config, array $options)
|
||||
{
|
||||
[$username, $password] = [
|
||||
$config['username'] ?? null, $config['password'] ?? null,
|
||||
];
|
||||
|
||||
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, $password, $options)
|
||||
{
|
||||
if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
|
||||
return new PDOConnection($dsn, $username, $password, $options);
|
||||
}
|
||||
|
||||
return new PDO($dsn, $username, $password, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the connection is persistent.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPersistentConnection($options)
|
||||
{
|
||||
return isset($options[PDO::ATTR_PERSISTENT]) &&
|
||||
$options[PDO::ATTR_PERSISTENT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception that occurred during connect execution.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param string $dsn
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param array $options
|
||||
* @return \PDO
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function tryAgainIfCausedByLostConnection(Throwable $e, $dsn, $username, $password, $options)
|
||||
{
|
||||
if ($this->causedByLostConnection($e)) {
|
||||
return $this->createPdoConnection($dsn, $username, $password, $options);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDO options based on the configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions(array $config)
|
||||
{
|
||||
$options = $config['options'] ?? [];
|
||||
|
||||
return array_diff_key($this->options, $options) + $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default PDO connection options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default PDO connection options.
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultOptions(array $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
interface ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* Establish a database connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \PDO
|
||||
*/
|
||||
public function connect(array $config);
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use PDO;
|
||||
|
||||
class MySqlConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* Establish a database connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \PDO
|
||||
*/
|
||||
public function connect(array $config)
|
||||
{
|
||||
$dsn = $this->getDsn($config);
|
||||
|
||||
$options = $this->getOptions($config);
|
||||
|
||||
// We need to grab the PDO options that should be used while making the brand
|
||||
// new connection instance. The PDO options control various aspects of the
|
||||
// connection's behavior, and some might be specified by the developers.
|
||||
$connection = $this->createConnection($dsn, $config, $options);
|
||||
|
||||
if (! empty($config['database'])) {
|
||||
$connection->exec("use `{$config['database']}`;");
|
||||
}
|
||||
|
||||
$this->configureIsolationLevel($connection, $config);
|
||||
|
||||
$this->configureEncoding($connection, $config);
|
||||
|
||||
// Next, we will check to see if a timezone has been specified in this config
|
||||
// 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);
|
||||
|
||||
$this->setModes($connection, $config);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection transaction isolation level.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureIsolationLevel($connection, array $config)
|
||||
{
|
||||
if (! isset($config['isolation_level'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare(
|
||||
"SET SESSION TRANSACTION ISOLATION LEVEL {$config['isolation_level']}"
|
||||
)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection character set and collation.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void|\PDO
|
||||
*/
|
||||
protected function configureEncoding($connection, array $config)
|
||||
{
|
||||
if (! isset($config['charset'])) {
|
||||
return $connection;
|
||||
}
|
||||
|
||||
$connection->prepare(
|
||||
"set names '{$config['charset']}'".$this->getCollation($config)
|
||||
)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collation for the configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getCollation(array $config)
|
||||
{
|
||||
return isset($config['collation']) ? " collate '{$config['collation']}'" : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timezone on the connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureTimezone($connection, array $config)
|
||||
{
|
||||
if (isset($config['timezone'])) {
|
||||
$connection->prepare('set time_zone="'.$config['timezone'].'"')->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DSN string from a configuration.
|
||||
*
|
||||
* Chooses socket or host/port based on the 'unix_socket' config value.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getDsn(array $config)
|
||||
{
|
||||
return $this->hasSocket($config)
|
||||
? $this->getSocketDsn($config)
|
||||
: $this->getHostDsn($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given configuration array has a UNIX socket value.
|
||||
*
|
||||
* @param array $config
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasSocket(array $config)
|
||||
{
|
||||
return isset($config['unix_socket']) && ! empty($config['unix_socket']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DSN string for a socket configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getSocketDsn(array $config)
|
||||
{
|
||||
return "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DSN string for a host / port configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getHostDsn(array $config)
|
||||
{
|
||||
extract($config, EXTR_SKIP);
|
||||
|
||||
return isset($port)
|
||||
? "mysql:host={$host};port={$port};dbname={$database}"
|
||||
: "mysql:host={$host};dbname={$database}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the modes for the connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function setModes(PDO $connection, array $config)
|
||||
{
|
||||
if (isset($config['modes'])) {
|
||||
$this->setCustomModes($connection, $config);
|
||||
} elseif (isset($config['strict'])) {
|
||||
if ($config['strict']) {
|
||||
$connection->prepare($this->strictMode($connection, $config))->execute();
|
||||
} else {
|
||||
$connection->prepare("set session sql_mode='NO_ENGINE_SUBSTITUTION'")->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom modes on the connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function setCustomModes(PDO $connection, array $config)
|
||||
{
|
||||
$modes = implode(',', $config['modes']);
|
||||
|
||||
$connection->prepare("set session sql_mode='{$modes}'")->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query to enable strict mode.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function strictMode(PDO $connection, $config)
|
||||
{
|
||||
$version = $config['version'] ?? $connection->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
|
||||
if (version_compare($version, '8.0.11') >= 0) {
|
||||
return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
|
||||
}
|
||||
|
||||
return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use PDO;
|
||||
|
||||
class PostgresConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* The default PDO connection options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Establish a database connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \PDO
|
||||
*/
|
||||
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)
|
||||
);
|
||||
|
||||
$this->configureEncoding($connection, $config);
|
||||
|
||||
// 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);
|
||||
|
||||
$this->configureSchema($connection, $config);
|
||||
|
||||
// Postgres allows an application_name to be set by the user and this name is
|
||||
// used to when monitoring the application with pg_stat_activity. So we'll
|
||||
// determine if the option has been specified and run a statement if so.
|
||||
$this->configureApplicationName($connection, $config);
|
||||
|
||||
$this->configureSynchronousCommit($connection, $config);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection character set and collation.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureEncoding($connection, $config)
|
||||
{
|
||||
if (! isset($config['charset'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare("set names '{$config['charset']}'")->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 schema on the connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSchema($connection, $config)
|
||||
{
|
||||
if (isset($config['schema'])) {
|
||||
$schema = $this->formatSchema($config['schema']);
|
||||
|
||||
$connection->prepare("set search_path to {$schema}")->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the schema for the DSN.
|
||||
*
|
||||
* @param array|string $schema
|
||||
* @return string
|
||||
*/
|
||||
protected function formatSchema($schema)
|
||||
{
|
||||
if (is_array($schema)) {
|
||||
return '"'.implode('", "', $schema).'"';
|
||||
}
|
||||
|
||||
return '"'.$schema.'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the schema on the connection.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureApplicationName($connection, $config)
|
||||
{
|
||||
if (isset($config['application_name'])) {
|
||||
$applicationName = $config['application_name'];
|
||||
|
||||
$connection->prepare("set application_name to '$applicationName'")->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DSN string from a configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
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.
|
||||
extract($config, EXTR_SKIP);
|
||||
|
||||
$host = isset($host) ? "host={$host};" : '';
|
||||
|
||||
$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 (isset($config['port'])) {
|
||||
$dsn .= ";port={$port}";
|
||||
}
|
||||
|
||||
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]}";
|
||||
}
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the synchronous_commit setting.
|
||||
*
|
||||
* @param \PDO $connection
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
protected function configureSynchronousCommit($connection, array $config)
|
||||
{
|
||||
if (! isset($config['synchronous_commit'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->prepare("set synchronous_commit to '{$config['synchronous_commit']}'")->execute();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SQLiteConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* Establish a database connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \PDO
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function connect(array $config)
|
||||
{
|
||||
$options = $this->getOptions($config);
|
||||
|
||||
// SQLite supports "in-memory" databases that only last as long as the owning
|
||||
// connection does. These are useful for tests or for short lifetime store
|
||||
// querying. In-memory databases may only have a single open connection.
|
||||
if ($config['database'] === ':memory:') {
|
||||
return $this->createConnection('sqlite::memory:', $config, $options);
|
||||
}
|
||||
|
||||
$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 InvalidArgumentException("Database ({$config['database']}) does not exist.");
|
||||
}
|
||||
|
||||
return $this->createConnection("sqlite:{$path}", $config, $options);
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Connectors;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use PDO;
|
||||
|
||||
class SqlServerConnector extends Connector implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* The PDO connection options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [
|
||||
PDO::ATTR_CASE => PDO::CASE_NATURAL,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Establish a database connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \PDO
|
||||
*/
|
||||
public function connect(array $config)
|
||||
{
|
||||
$options = $this->getOptions($config);
|
||||
|
||||
return $this->createConnection($this->getDsn($config), $config, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DSN string from a configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
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)) {
|
||||
return $this->getOdbcDsn($config);
|
||||
}
|
||||
|
||||
if (in_array('sqlsrv', $this->getAvailableDrivers())) {
|
||||
return $this->getSqlSrvDsn($config);
|
||||
} else {
|
||||
return $this->getDblibDsn($config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the database configuration prefers ODBC.
|
||||
*
|
||||
* @param array $config
|
||||
* @return bool
|
||||
*/
|
||||
protected function prefersOdbc(array $config)
|
||||
{
|
||||
return in_array('odbc', $this->getAvailableDrivers()) &&
|
||||
($config['odbc'] ?? null) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DSN string for a DbLib connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getDblibDsn(array $config)
|
||||
{
|
||||
return $this->buildConnectString('dblib', array_merge([
|
||||
'host' => $this->buildHostString($config, ':'),
|
||||
'dbname' => $config['database'],
|
||||
], Arr::only($config, ['appname', 'charset', 'version'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DSN string for an ODBC connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getOdbcDsn(array $config)
|
||||
{
|
||||
return isset($config['odbc_datasource_name'])
|
||||
? 'odbc:'.$config['odbc_datasource_name'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DSN string for a SqlSrv connection.
|
||||
*
|
||||
* @param array $config
|
||||
* @return string
|
||||
*/
|
||||
protected function getSqlSrvDsn(array $config)
|
||||
{
|
||||
$arguments = [
|
||||
'Server' => $this->buildHostString($config, ','),
|
||||
];
|
||||
|
||||
if (isset($config['database'])) {
|
||||
$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'];
|
||||
}
|
||||
|
||||
return $this->buildConnectString('sqlsrv', $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a connection string from the given arguments.
|
||||
*
|
||||
* @param string $driver
|
||||
* @param array $arguments
|
||||
* @return string
|
||||
*/
|
||||
protected function buildConnectString($driver, array $arguments)
|
||||
{
|
||||
return $driver.':'.implode(';', array_map(function ($key) use ($arguments) {
|
||||
return sprintf('%s=%s', $key, $arguments[$key]);
|
||||
}, array_keys($arguments)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a host string from the given configuration.
|
||||
*
|
||||
* @param array $config
|
||||
* @param string $separator
|
||||
* @return string
|
||||
*/
|
||||
protected function buildHostString(array $config, $separator)
|
||||
{
|
||||
if (empty($config['port'])) {
|
||||
return $config['host'];
|
||||
}
|
||||
|
||||
return $config['host'].$separator.$config['port'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available PDO drivers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAvailableDrivers()
|
||||
{
|
||||
return PDO::getAvailableDrivers();
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\ConfigurationUrlParser;
|
||||
use Symfony\Component\Process\Process;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class DbCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db {connection? : The database connection that should be used}
|
||||
{--read : Connect to the read connection}
|
||||
{--write : Connect to the write connection}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Start a new database CLI session';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
|
||||
(new Process(
|
||||
array_merge([$this->getCommand($connection)], $this->commandArguments($connection)),
|
||||
null,
|
||||
$this->commandEnvironment($connection)
|
||||
))->setTimeout(null)->setTty(true)->mustRun(function ($type, $buffer) {
|
||||
$this->output->write($buffer);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection configuration.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
$connection = $this->laravel['config']['database.connections.'.
|
||||
(($db = $this->argument('connection')) ?? $this->laravel['config']['database.default'])
|
||||
];
|
||||
|
||||
if (empty($connection)) {
|
||||
throw new UnexpectedValueException("Invalid database connection [{$db}].");
|
||||
}
|
||||
|
||||
if (! empty($connection['url'])) {
|
||||
$connection = (new ConfigurationUrlParser)->parseConfiguration($connection);
|
||||
}
|
||||
|
||||
if ($this->option('read')) {
|
||||
$connection = array_merge($connection, $connection['read']);
|
||||
} elseif ($this->option('write')) {
|
||||
$connection = array_merge($connection, $connection['write']);
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the database client command.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
public function commandArguments(array $connection)
|
||||
{
|
||||
$driver = ucfirst($connection['driver']);
|
||||
|
||||
return $this->{"get{$driver}Arguments"}($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables for the database client command.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array|null
|
||||
*/
|
||||
public function commandEnvironment(array $connection)
|
||||
{
|
||||
$driver = ucfirst($connection['driver']);
|
||||
|
||||
if (method_exists($this, "get{$driver}Environment")) {
|
||||
return $this->{"get{$driver}Environment"}($connection);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database client command to run.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand(array $connection)
|
||||
{
|
||||
return [
|
||||
'mysql' => 'mysql',
|
||||
'pgsql' => 'psql',
|
||||
'sqlite' => 'sqlite3',
|
||||
'sqlsrv' => 'sqlcmd',
|
||||
][$connection['driver']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the MySQL CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getMysqlArguments(array $connection)
|
||||
{
|
||||
return array_merge([
|
||||
'--host='.$connection['host'],
|
||||
'--port='.$connection['port'],
|
||||
'--user='.$connection['username'],
|
||||
], $this->getOptionalArguments([
|
||||
'password' => '--password='.$connection['password'],
|
||||
'unix_socket' => '--socket='.$connection['unix_socket'],
|
||||
'charset' => '--default-character-set='.$connection['charset'],
|
||||
], $connection), [$connection['database']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the Postgres CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getPgsqlArguments(array $connection)
|
||||
{
|
||||
return [$connection['database']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the SQLite CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getSqliteArguments(array $connection)
|
||||
{
|
||||
return [$connection['database']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the SQL Server CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getSqlsrvArguments(array $connection)
|
||||
{
|
||||
return array_merge(...$this->getOptionalArguments([
|
||||
'database' => ['-d', $connection['database']],
|
||||
'username' => ['-U', $connection['username']],
|
||||
'password' => ['-P', $connection['password']],
|
||||
'host' => ['-S', 'tcp:'.$connection['host']
|
||||
.($connection['port'] ? ','.$connection['port'] : ''), ],
|
||||
], $connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the environment variables for the Postgres CLI.
|
||||
*
|
||||
* @param array $connection
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getPgsqlEnvironment(array $connection)
|
||||
{
|
||||
return array_merge(...$this->getOptionalArguments([
|
||||
'username' => ['PGUSER' => $connection['username']],
|
||||
'host' => ['PGHOST' => $connection['host']],
|
||||
'port' => ['PGPORT' => $connection['port']],
|
||||
'password' => ['PGPASSWORD' => $connection['password']],
|
||||
], $connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optional arguments based on the connection configuration.
|
||||
*
|
||||
* @param array $args
|
||||
* @param array $connection
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptionalArguments(array $args, array $connection)
|
||||
{
|
||||
return array_values(array_filter($args, function ($key) use ($connection) {
|
||||
return ! empty($connection[$key]);
|
||||
}, ARRAY_FILTER_USE_KEY));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Database\Events\SchemaDumped;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class DumpCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'schema:dump
|
||||
{--database= : The database connection to use}
|
||||
{--path= : The path where the schema dump file should be stored}
|
||||
{--prune : Delete all existing migration files}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dump the given database schema';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $connections
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher)
|
||||
{
|
||||
$connection = $connections->connection($database = $this->input->getOption('database'));
|
||||
|
||||
$this->schemaState($connection)->dump(
|
||||
$connection, $path = $this->path($connection)
|
||||
);
|
||||
|
||||
$dispatcher->dispatch(new SchemaDumped($connection, $path));
|
||||
|
||||
$this->info('Database schema dumped successfully.');
|
||||
|
||||
if ($this->option('prune')) {
|
||||
(new Filesystem)->deleteDirectory(
|
||||
database_path('migrations'), $preserve = false
|
||||
);
|
||||
|
||||
$this->info('Migrations pruned successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a schema state instance for the given connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @return mixed
|
||||
*/
|
||||
protected function schemaState(Connection $connection)
|
||||
{
|
||||
return $connection->getSchemaState()
|
||||
->withMigrationTable($connection->getTablePrefix().Config::get('database.migrations', 'migrations'))
|
||||
->handleOutputUsing(function ($type, $buffer) {
|
||||
$this->output->write($buffer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path that the dump should be written to.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
*/
|
||||
protected function path(Connection $connection)
|
||||
{
|
||||
return tap($this->option('path') ?: database_path('schema/'.$connection->getName().'-schema.dump'), function ($path) {
|
||||
(new Filesystem)->ensureDirectoryExists(dirname($path));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Factories;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class FactoryMakeCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:factory';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new model factory';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Factory';
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/factory.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.$stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name)));
|
||||
|
||||
$namespaceModel = $this->option('model')
|
||||
? $this->qualifyModel($this->option('model'))
|
||||
: $this->qualifyModel($this->guessModelName($name));
|
||||
|
||||
$model = class_basename($namespaceModel);
|
||||
|
||||
if (Str::startsWith($namespaceModel, $this->rootNamespace().'Models')) {
|
||||
$namespace = Str::beforeLast('Database\\Factories\\'.Str::after($namespaceModel, $this->rootNamespace().'Models\\'), '\\');
|
||||
} else {
|
||||
$namespace = 'Database\\Factories';
|
||||
}
|
||||
|
||||
$replace = [
|
||||
'{{ factoryNamespace }}' => $namespace,
|
||||
'NamespacedDummyModel' => $namespaceModel,
|
||||
'{{ namespacedModel }}' => $namespaceModel,
|
||||
'{{namespacedModel}}' => $namespaceModel,
|
||||
'DummyModel' => $model,
|
||||
'{{ model }}' => $model,
|
||||
'{{model}}' => $model,
|
||||
'{{ factory }}' => $factory,
|
||||
'{{factory}}' => $factory,
|
||||
];
|
||||
|
||||
return str_replace(
|
||||
array_keys($replace), array_values($replace), parent::buildClass($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination class path.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getPath($name)
|
||||
{
|
||||
$name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '')->finish('Factory');
|
||||
|
||||
return $this->laravel->databasePath().'/factories/'.str_replace('\\', '/', $name).'.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the model name from the Factory name or return a default model name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function guessModelName($name)
|
||||
{
|
||||
if (Str::endsWith($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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace {{ factoryNamespace }};
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use {{ namespacedModel }};
|
||||
|
||||
class {{ factory }}Factory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = {{ model }}::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class BaseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Get all of the migration paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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 collect($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.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getMigrationPath()
|
||||
{
|
||||
return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'migrations';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\DatabaseRefreshed;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class FreshCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:fresh';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables and re-run all migrations';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
$this->call('db:wipe', array_filter([
|
||||
'--database' => $database,
|
||||
'--drop-views' => $this->option('drop-views'),
|
||||
'--drop-types' => $this->option('drop-types'),
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
$this->call('migrate', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $this->input->getOption('path'),
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--schema-path' => $this->input->getOption('schema-path'),
|
||||
'--force' => true,
|
||||
'--step' => $this->option('step'),
|
||||
]));
|
||||
|
||||
if ($this->laravel->bound(Dispatcher::class)) {
|
||||
$this->laravel[Dispatcher::class]->dispatch(
|
||||
new DatabaseRefreshed
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->needsSeeding()) {
|
||||
$this->runSeeder($database);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the developer has requested database seeding.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function needsSeeding()
|
||||
{
|
||||
return $this->option('seed') || $this->option('seeder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeder command.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function runSeeder($database)
|
||||
{
|
||||
$this->call('db:seed', array_filter([
|
||||
'--database' => $database,
|
||||
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
|
||||
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
['schema-path', null, InputOption::VALUE_OPTIONAL, 'The path to a schema dump file'],
|
||||
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'],
|
||||
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'],
|
||||
['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:install';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create the migration repository';
|
||||
|
||||
/**
|
||||
* The repository instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* Create a new migration install command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(MigrationRepositoryInterface $repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->repository->setSource($this->input->getOption('database'));
|
||||
|
||||
$this->repository->createRepository();
|
||||
|
||||
$this->info('Migration table created successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\SchemaLoaded;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
|
||||
class MigrateCommand extends BaseCommand
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @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}
|
||||
{--step : Force the migrations to be run so they can be rolled back individually}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run the database migrations';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
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)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->migrator->usingConnection($this->option('database'), function () {
|
||||
$this->prepareDatabase();
|
||||
|
||||
// Next, we will check to see if a path option has been defined. If it has
|
||||
// we will use the path relative to the root of this installation folder
|
||||
// so that migrations may be run for any path within the applications.
|
||||
$this->migrator->setOutput($this->output)
|
||||
->run($this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => $this->option('step'),
|
||||
]);
|
||||
|
||||
// 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', ['--force' => true]);
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the migration database for running.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareDatabase()
|
||||
{
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
$this->call('migrate:install', array_filter([
|
||||
'--database' => $this->option('database'),
|
||||
]));
|
||||
}
|
||||
|
||||
if (! $this->migrator->hasRunAnyMigrations() && ! $this->option('pretend')) {
|
||||
$this->loadSchemaState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->line('<info>Loading stored database schema:</info> '.$path);
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
// 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);
|
||||
|
||||
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
// 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)
|
||||
);
|
||||
|
||||
$this->line('<info>Loaded stored database schema.</info> ('.$runTime.'ms)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the stored schema for the given connection.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @return string
|
||||
*/
|
||||
protected function schemaPath($connection)
|
||||
{
|
||||
if ($this->option('schema-path')) {
|
||||
return $this->option('schema-path');
|
||||
}
|
||||
|
||||
if (file_exists($path = database_path('schema/'.$connection->getName().'-schema.dump'))) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return database_path('schema/'.$connection->getName().'-schema.sql');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Database\Migrations\MigrationCreator;
|
||||
use Illuminate\Support\Composer;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
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}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new migration file';
|
||||
|
||||
/**
|
||||
* The migration creator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\MigrationCreator
|
||||
*/
|
||||
protected $creator;
|
||||
|
||||
/**
|
||||
* The Composer instance.
|
||||
*
|
||||
* @var \Illuminate\Support\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Create a new migration install command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\MigrationCreator $creator
|
||||
* @param \Illuminate\Support\Composer $composer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(MigrationCreator $creator, Composer $composer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->creator = $creator;
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// It's possible for the developer to specify the tables to modify in this
|
||||
// schema operation. The developer may also specify if this table needs
|
||||
// to be freshly created so we can create the appropriate migrations.
|
||||
$name = Str::snake(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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration file to disk.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $table
|
||||
* @param bool $create
|
||||
* @return string
|
||||
*/
|
||||
protected function writeMigration($name, $table, $create)
|
||||
{
|
||||
$file = $this->creator->create(
|
||||
$name, $this->getMigrationPath(), $table, $create
|
||||
);
|
||||
|
||||
if (! $this->option('fullpath')) {
|
||||
$file = pathinfo($file, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
$this->line("<info>Created Migration:</info> {$file}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get migration path (either specified by '--path' option or default location).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getMigrationPath()
|
||||
{
|
||||
if (! is_null($targetPath = $this->input->getOption('path'))) {
|
||||
return ! $this->usingRealPath()
|
||||
? $this->laravel->basePath().'/'.$targetPath
|
||||
: $targetPath;
|
||||
}
|
||||
|
||||
return parent::getMigrationPath();
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\DatabaseRefreshed;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class RefreshCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:refresh';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Reset and re-run all migrations';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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');
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
// 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([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
if ($this->laravel->bound(Dispatcher::class)) {
|
||||
$this->laravel[Dispatcher::class]->dispatch(
|
||||
new DatabaseRefreshed
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->needsSeeding()) {
|
||||
$this->runSeeder($database);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'],
|
||||
['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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class ResetCommand extends BaseCommand
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:reset';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rollback all database migrations';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
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->comment('Migration table not found.');
|
||||
}
|
||||
|
||||
$this->migrator->setOutput($this->output)->reset(
|
||||
$this->getMigrationPaths(), $this->option('pretend')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class RollbackCommand extends BaseCommand
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:rollback';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rollback the last database migration';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->migrator->usingConnection($this->option('database'), function () {
|
||||
$this->migrator->setOutput($this->output)->rollback(
|
||||
$this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => (int) $this->option('step'),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class StatusCommand extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:status';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Show the status of each migration';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
return $this->migrator->usingConnection($this->option('database'), function () {
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
$this->error('Migration table not found.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$ran = $this->migrator->getRepository()->getRan();
|
||||
|
||||
$batches = $this->migrator->getRepository()->getMigrationBatches();
|
||||
|
||||
if (count($migrations = $this->getStatusFor($ran, $batches)) > 0) {
|
||||
$this->table(['Ran?', 'Migration', 'Batch'], $migrations);
|
||||
} else {
|
||||
$this->error('No migrations found');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status for the given ran migrations.
|
||||
*
|
||||
* @param array $ran
|
||||
* @param array $batches
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getStatusFor(array $ran, array $batches)
|
||||
{
|
||||
return Collection::make($this->getAllMigrationFiles())
|
||||
->map(function ($migration) use ($ran, $batches) {
|
||||
$migrationName = $this->migrator->getMigrationName($migration);
|
||||
|
||||
return in_array($migrationName, $ran)
|
||||
? ['<info>Yes</info>', $migrationName, $batches[$migrationName]]
|
||||
: ['<fg=red>No</fg=red>', $migrationName];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all of the migration files.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllMigrationFiles()
|
||||
{
|
||||
return $this->migrator->getMigrationFiles($this->getMigrationPaths());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to use'],
|
||||
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
class TableGuesser
|
||||
{
|
||||
const CREATE_PATTERNS = [
|
||||
'/^create_(\w+)_table$/',
|
||||
'/^create_(\w+)$/',
|
||||
];
|
||||
|
||||
const CHANGE_PATTERNS = [
|
||||
'/_(to|from|in)_(\w+)_table$/',
|
||||
'/_(to|from|in)_(\w+)$/',
|
||||
];
|
||||
|
||||
/**
|
||||
* Attempt to guess the table name and "creation" status of the given migration.
|
||||
*
|
||||
* @param string $migration
|
||||
* @return array
|
||||
*/
|
||||
public static function guess($migration)
|
||||
{
|
||||
foreach (self::CREATE_PATTERNS as $pattern) {
|
||||
if (preg_match($pattern, $migration, $matches)) {
|
||||
return [$matches[1], $create = true];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::CHANGE_PATTERNS as $pattern) {
|
||||
if (preg_match($pattern, $migration, $matches)) {
|
||||
return [$matches[2], $create = false];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\MassPrunable;
|
||||
use Illuminate\Database\Eloquent\Prunable;
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class PruneCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'model:prune
|
||||
{--model=* : Class names of the models to be pruned}
|
||||
{--chunk=1000 : The number of models to retrieve per chunk of models to be deleted}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Prune models that are no longer needed';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Dispatcher $events)
|
||||
{
|
||||
$models = $this->models();
|
||||
|
||||
if ($models->isEmpty()) {
|
||||
$this->info('No prunable models found.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$events->listen(ModelsPruned::class, function ($event) {
|
||||
$this->info("{$event->count} [{$event->model}] records have been pruned.");
|
||||
});
|
||||
|
||||
$models->each(function ($model) {
|
||||
$instance = new $model;
|
||||
|
||||
$chunkSize = property_exists($instance, 'prunableChunkSize')
|
||||
? $instance->prunableChunkSize
|
||||
: $this->option('chunk');
|
||||
|
||||
$total = $this->isPrunable($model)
|
||||
? $instance->pruneAll($chunkSize)
|
||||
: 0;
|
||||
|
||||
if ($total == 0) {
|
||||
$this->info("No prunable [$model] records found.");
|
||||
}
|
||||
});
|
||||
|
||||
$events->forget(ModelsPruned::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the models that should be pruned.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function models()
|
||||
{
|
||||
if (! empty($models = $this->option('model'))) {
|
||||
return collect($models);
|
||||
}
|
||||
|
||||
return collect((new Finder)->in(app_path('Models'))->files())
|
||||
->map(function ($model) {
|
||||
$namespace = $this->laravel->getNamespace();
|
||||
|
||||
return $namespace.str_replace(
|
||||
['/', '.php'],
|
||||
['\\', ''],
|
||||
Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR)
|
||||
);
|
||||
})->filter(function ($model) {
|
||||
return $this->isPrunable($model);
|
||||
})->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given model class is prunable.
|
||||
*
|
||||
* @param string $model
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPrunable($model)
|
||||
{
|
||||
$uses = class_uses_recursive($model);
|
||||
|
||||
return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Database\ConnectionResolverInterface as Resolver;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class SeedCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:seed';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Seed the database with records';
|
||||
|
||||
/**
|
||||
* The connection resolver instance.
|
||||
*
|
||||
* @var \Illuminate\Database\ConnectionResolverInterface
|
||||
*/
|
||||
protected $resolver;
|
||||
|
||||
/**
|
||||
* Create a new database seed command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Resolver $resolver)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$previousConnection = $this->resolver->getDefaultConnection();
|
||||
|
||||
$this->resolver->setDefaultConnection($this->getDatabase());
|
||||
|
||||
Model::unguarded(function () {
|
||||
$this->getSeeder()->__invoke();
|
||||
});
|
||||
|
||||
if ($previousConnection) {
|
||||
$this->resolver->setDefaultConnection($previousConnection);
|
||||
}
|
||||
|
||||
$this->info('Database seeding completed successfully.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a seeder instance from the container.
|
||||
*
|
||||
* @return \Illuminate\Database\Seeder
|
||||
*/
|
||||
protected function getSeeder()
|
||||
{
|
||||
$class = $this->input->getArgument('class') ?? $this->input->getOption('class');
|
||||
|
||||
if (strpos($class, '\\') === false) {
|
||||
$class = 'Database\\Seeders\\'.$class;
|
||||
}
|
||||
|
||||
if ($class === 'Database\\Seeders\\DatabaseSeeder' &&
|
||||
! class_exists($class)) {
|
||||
$class = 'DatabaseSeeder';
|
||||
}
|
||||
|
||||
return $this->laravel->make($class)
|
||||
->setContainer($this->laravel)
|
||||
->setCommand($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the database connection to use.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDatabase()
|
||||
{
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
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.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'],
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
|
||||
class SeederMakeCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:seeder';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new seeder class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Seeder';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
parent::handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/seeder.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.$stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination class path.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getPath($name)
|
||||
{
|
||||
if (is_dir($this->laravel->databasePath().'/seeds')) {
|
||||
return $this->laravel->databasePath().'/seeds/'.$name.'.php';
|
||||
} else {
|
||||
return $this->laravel->databasePath().'/seeders/'.$name.'.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the class name and format according to the root namespace.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function qualifyClass($name)
|
||||
{
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class {{ class }} extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class WipeCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:wipe';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables, views, and types';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
if ($this->option('drop-views')) {
|
||||
$this->dropAllViews($database);
|
||||
|
||||
$this->info('Dropped all views successfully.');
|
||||
}
|
||||
|
||||
$this->dropAllTables($database);
|
||||
|
||||
$this->info('Dropped all tables successfully.');
|
||||
|
||||
if ($this->option('drop-types')) {
|
||||
$this->dropAllTypes($database);
|
||||
|
||||
$this->info('Dropped all types successfully.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all of the database tables.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function dropAllTables($database)
|
||||
{
|
||||
$this->laravel['db']->connection($database)
|
||||
->getSchemaBuilder()
|
||||
->dropAllTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all of the database views.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function dropAllViews($database)
|
||||
{
|
||||
$this->laravel['db']->connection($database)
|
||||
->getSchemaBuilder()
|
||||
->dropAllViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all of the database types.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function dropAllTypes($database)
|
||||
{
|
||||
$this->laravel['db']->connection($database)
|
||||
->getSchemaBuilder()
|
||||
->dropAllTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
|
||||
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\DBAL;
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
class TimestampType extends Type
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
$name = $platform->getName();
|
||||
|
||||
switch ($name) {
|
||||
case 'mysql':
|
||||
case 'mysql2':
|
||||
return $this->getMySqlPlatformSQLDeclaration($fieldDeclaration);
|
||||
|
||||
case 'postgresql':
|
||||
case 'pgsql':
|
||||
case 'postgres':
|
||||
return $this->getPostgresPlatformSQLDeclaration($fieldDeclaration);
|
||||
|
||||
case 'mssql':
|
||||
return $this->getSqlServerPlatformSQLDeclaration($fieldDeclaration);
|
||||
|
||||
case 'sqlite':
|
||||
case 'sqlite3':
|
||||
return $this->getSQLitePlatformSQLDeclaration($fieldDeclaration);
|
||||
|
||||
default:
|
||||
throw new DBALException('Invalid platform: '.$name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for MySQL.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getMySqlPlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
$columnType = 'TIMESTAMP';
|
||||
|
||||
if ($fieldDeclaration['precision']) {
|
||||
$columnType = 'TIMESTAMP('.$fieldDeclaration['precision'].')';
|
||||
}
|
||||
|
||||
$notNull = $fieldDeclaration['notnull'] ?? false;
|
||||
|
||||
if (! $notNull) {
|
||||
return $columnType.' NULL';
|
||||
}
|
||||
|
||||
return $columnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for PostgreSQL.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getPostgresPlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
return 'TIMESTAMP('.(int) $fieldDeclaration['precision'].')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for SQL Server.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getSqlServerPlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
return $fieldDeclaration['precision'] ?? false
|
||||
? 'DATETIME2('.$fieldDeclaration['precision'].')'
|
||||
: 'DATETIME';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL declaration for SQLite.
|
||||
*
|
||||
* @param array $fieldDeclaration
|
||||
* @return string
|
||||
*/
|
||||
protected function getSQLitePlatformSQLDeclaration(array $fieldDeclaration)
|
||||
{
|
||||
return 'DATETIME';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'timestamp';
|
||||
}
|
||||
}
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Illuminate\Database\Connectors\ConnectionFactory;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\ConfigurationUrlParser;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Database\Connection
|
||||
*/
|
||||
class DatabaseManager implements ConnectionResolverInterface
|
||||
{
|
||||
/**
|
||||
* The application instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Foundation\Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* The database connection factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Connectors\ConnectionFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The active connection instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $connections = [];
|
||||
|
||||
/**
|
||||
* The custom connection resolvers.
|
||||
*
|
||||
* @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\Database\Connectors\ConnectionFactory $factory
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($app, ConnectionFactory $factory)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->factory = $factory;
|
||||
|
||||
$this->reconnector = function ($connection) {
|
||||
$this->reconnect($connection->getNameWithReadWriteType());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a database connection instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function connection($name = null)
|
||||
{
|
||||
[$database, $type] = $this->parseConnectionName($name);
|
||||
|
||||
$name = $name ?: $database;
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
|
||||
return $this->connections[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the connection into an array of the name and read / write type.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
protected function parseConnectionName($name)
|
||||
{
|
||||
$name = $name ?: $this->getDefaultConnection();
|
||||
|
||||
return Str::endsWith($name, ['::read', '::write'])
|
||||
? explode('::', $name, 2) : [$name, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the database connection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
protected function makeConnection($name)
|
||||
{
|
||||
$config = $this->configuration($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
|
||||
// Closure and pass it the config allowing it to resolve the connection.
|
||||
if (isset($this->extensions[$name])) {
|
||||
return call_user_func($this->extensions[$name], $config, $name);
|
||||
}
|
||||
|
||||
// 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']])) {
|
||||
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)
|
||||
{
|
||||
$name = $name ?: $this->getDefaultConnection();
|
||||
|
||||
// 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'];
|
||||
|
||||
if (is_null($config = Arr::get($connections, $name))) {
|
||||
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)
|
||||
{
|
||||
$connection = $this->setPdoForType($connection, $type)->setReadWriteType($type);
|
||||
|
||||
// First we'll set the fetch mode and a few other dependencies of the database
|
||||
// connection. This method basically just configures and prepares it to get
|
||||
// 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);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the read / write mode for database connection instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @param string|null $type
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
protected function setPdoForType(Connection $connection, $type = null)
|
||||
{
|
||||
if ($type === 'read') {
|
||||
$connection->setPdo($connection->getReadPdo());
|
||||
} elseif ($type === 'write') {
|
||||
$connection->setReadPdo($connection->getPdo());
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the given database and remove from local cache.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function purge($name = null)
|
||||
{
|
||||
$name = $name ?: $this->getDefaultConnection();
|
||||
|
||||
$this->disconnect($name);
|
||||
|
||||
unset($this->connections[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the given database.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect($name = null)
|
||||
{
|
||||
if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) {
|
||||
$this->connections[$name]->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to the given database.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function reconnect($name = null)
|
||||
{
|
||||
$this->disconnect($name = $name ?: $this->getDefaultConnection());
|
||||
|
||||
if (! isset($this->connections[$name])) {
|
||||
return $this->connection($name);
|
||||
}
|
||||
|
||||
return $this->refreshPdoConnections($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default database connection for the callback execution.
|
||||
*
|
||||
* @param string $name
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public function usingConnection($name, callable $callback)
|
||||
{
|
||||
$previousName = $this->getDefaultConnection();
|
||||
|
||||
$this->setDefaultConnection($name);
|
||||
|
||||
return tap($callback(), function () use ($previousName) {
|
||||
$this->setDefaultConnection($previousName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the PDO connections on a given connection.
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default connection name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultConnection()
|
||||
{
|
||||
return $this->app['config']['database.default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default connection name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultConnection($name)
|
||||
{
|
||||
$this->app['config']['database.default'] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the support drivers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function supportedDrivers()
|
||||
{
|
||||
return ['mysql', 'pgsql', 'sqlite', 'sqlsrv'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the drivers that are actually available.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function availableDrivers()
|
||||
{
|
||||
return array_intersect(
|
||||
$this->supportedDrivers(),
|
||||
str_replace('dblib', 'sqlsrv', PDO::getAvailableDrivers())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an extension connection resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param callable $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function extend($name, callable $resolver)
|
||||
{
|
||||
$this->extensions[$name] = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the created connections.
|
||||
*
|
||||
* @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
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
return $this->connection()->$method(...$parameters);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Faker\Factory as FakerFactory;
|
||||
use Faker\Generator as FakerGenerator;
|
||||
use Illuminate\Contracts\Queue\EntityResolver;
|
||||
use Illuminate\Database\Connectors\ConnectionFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\QueueEntityResolver;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class DatabaseServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The array of resolved Faker instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $fakers = [];
|
||||
|
||||
/**
|
||||
* Bootstrap the application events.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Model::setConnectionResolver($this->app['db']);
|
||||
|
||||
Model::setEventDispatcher($this->app['events']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
Model::clearBootedModels();
|
||||
|
||||
$this->registerConnectionServices();
|
||||
$this->registerEloquentFactory();
|
||||
$this->registerQueueableEntityResolver();
|
||||
$this->registerDoctrineTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
$this->app->singleton('db.factory', function ($app) {
|
||||
return new ConnectionFactory($app);
|
||||
});
|
||||
|
||||
// The database manager is used to resolve various connections, since multiple
|
||||
// connections might be managed. It also implements the connection resolver
|
||||
// interface which may be used by other components requiring connections.
|
||||
$this->app->singleton('db', function ($app) {
|
||||
return new DatabaseManager($app, $app['db.factory']);
|
||||
});
|
||||
|
||||
$this->app->bind('db.connection', function ($app) {
|
||||
return $app['db']->connection();
|
||||
});
|
||||
|
||||
$this->app->singleton('db.transactions', function ($app) {
|
||||
return new DatabaseTransactionsManager;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Eloquent factory instance in the container.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerEloquentFactory()
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
static::$fakers[$locale]->unique(true);
|
||||
|
||||
return static::$fakers[$locale];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the queueable entity resolver implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerQueueableEntityResolver()
|
||||
{
|
||||
$this->app->singleton(EntityResolver::class, function () {
|
||||
return new QueueEntityResolver;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom types with the Doctrine DBAL library.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function registerDoctrineTypes()
|
||||
{
|
||||
if (! class_exists(Type::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$types = $this->app['config']->get('database.dbal.types', []);
|
||||
|
||||
foreach ($types as $name => $class) {
|
||||
if (! Type::hasType($name)) {
|
||||
Type::addType($name, $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
class DatabaseTransactionRecord
|
||||
{
|
||||
/**
|
||||
* The name of the database connection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $connection;
|
||||
|
||||
/**
|
||||
* The transaction level.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $level;
|
||||
|
||||
/**
|
||||
* The callbacks that should be executed after committing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $callbacks = [];
|
||||
|
||||
/**
|
||||
* Create a new database transaction record instance.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param int $level
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($connection, $level)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be executed after committing.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public function addCallback($callback)
|
||||
{
|
||||
$this->callbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all of the callbacks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function executeCallbacks()
|
||||
{
|
||||
foreach ($this->callbacks as $callback) {
|
||||
call_user_func($callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the callbacks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCallbacks()
|
||||
{
|
||||
return $this->callbacks;
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
class DatabaseTransactionsManager
|
||||
{
|
||||
/**
|
||||
* All of the recorded transactions.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $transactions;
|
||||
|
||||
/**
|
||||
* Create a new database transactions manager instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->transactions = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new database transaction.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param int $level
|
||||
* @return void
|
||||
*/
|
||||
public function begin($connection, $level)
|
||||
{
|
||||
$this->transactions->push(
|
||||
new DatabaseTransactionRecord($connection, $level)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the active database transaction.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param int $level
|
||||
* @return void
|
||||
*/
|
||||
public function rollback($connection, $level)
|
||||
{
|
||||
$this->transactions = $this->transactions->reject(function ($transaction) use ($connection, $level) {
|
||||
return $transaction->connection == $connection &&
|
||||
$transaction->level > $level;
|
||||
})->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the active database transaction.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return void
|
||||
*/
|
||||
public function commit($connection)
|
||||
{
|
||||
[$forThisConnection, $forOtherConnections] = $this->transactions->partition(
|
||||
function ($transaction) use ($connection) {
|
||||
return $transaction->connection == $connection;
|
||||
}
|
||||
);
|
||||
|
||||
$this->transactions = $forOtherConnections->values();
|
||||
|
||||
$forThisConnection->map->executeCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a transaction callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public function addCallback($callback)
|
||||
{
|
||||
if ($current = $this->transactions->last()) {
|
||||
return $current->addCallback($callback);
|
||||
}
|
||||
|
||||
call_user_func($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the transactions.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getTransactions()
|
||||
{
|
||||
return $this->transactions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use PDOException;
|
||||
use Throwable;
|
||||
|
||||
trait DetectsConcurrencyErrors
|
||||
{
|
||||
/**
|
||||
* Determine if the given exception was caused by a concurrency error such as a deadlock or serialization failure.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return bool
|
||||
*/
|
||||
protected function causedByConcurrencyError(Throwable $e)
|
||||
{
|
||||
if ($e instanceof PDOException && $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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
trait DetectsLostConnections
|
||||
{
|
||||
/**
|
||||
* Determine if the given exception was caused by a lost connection.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return bool
|
||||
*/
|
||||
protected function causedByLostConnection(Throwable $e)
|
||||
{
|
||||
$message = $e->getMessage();
|
||||
|
||||
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',
|
||||
'Error writing data to the connection',
|
||||
'Resource deadlock avoided',
|
||||
'Transaction() on null',
|
||||
'child connection forced to terminate due to client_idle_limit',
|
||||
'query_wait_timeout',
|
||||
'reset by peer',
|
||||
'Physical connection is not usable',
|
||||
'TCP Provider: Error code 0x68',
|
||||
'ORA-03114',
|
||||
'Packets out of order. Expected',
|
||||
'Adaptive Server connection failed',
|
||||
'Communication link failure',
|
||||
'connection is no longer usable',
|
||||
'Login timeout expired',
|
||||
'SQLSTATE[HY000] [2002] Connection refused',
|
||||
'running with the --read-only option so it cannot execute this statement',
|
||||
'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
|
||||
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
|
||||
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
|
||||
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
|
||||
'SQLSTATE[HY000] [2002] Connection timed out',
|
||||
'SSL: Connection timed out',
|
||||
'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
|
||||
'Temporary failure in name resolution',
|
||||
'SSL: Broken pipe',
|
||||
'SQLSTATE[08S01]: Communication link failure',
|
||||
'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',
|
||||
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BroadcastableModelEventOccurred implements ShouldBroadcast
|
||||
{
|
||||
use InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* The model instance corresponding to the event.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* The event name (created, updated, etc.).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $event;
|
||||
|
||||
/**
|
||||
* The channels that the event should be broadcast on.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $channels = [];
|
||||
|
||||
/**
|
||||
* The queue connection that should be used to queue the broadcast job.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $connection;
|
||||
|
||||
/**
|
||||
* The queue that should be used to queue the broadcast job.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $queue;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $event
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($model, $event)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* The channels the event should broadcast on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
$channels = empty($this->channels)
|
||||
? ($this->model->broadcastOn($this->event) ?: [])
|
||||
: $this->channels;
|
||||
|
||||
return collect($channels)->map(function ($channel) {
|
||||
return $channel instanceof Model ? new PrivateChannel($channel) : $channel;
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The name the event should broadcast as.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function broadcastAs()
|
||||
{
|
||||
$default = class_basename($this->model).ucfirst($this->event);
|
||||
|
||||
return method_exists($this->model, 'broadcastAs')
|
||||
? ($this->model->broadcastAs($this->event) ?: $default)
|
||||
: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data that should be sent with the broadcasted event.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function broadcastWith()
|
||||
{
|
||||
return method_exists($this->model, 'broadcastWith')
|
||||
? $this->model->broadcastWith($this->event)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually specify the channels the event should broadcast on.
|
||||
*
|
||||
* @param array $channels
|
||||
* @return $this
|
||||
*/
|
||||
public function onChannels(array $channels)
|
||||
{
|
||||
$this->channels = $channels;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the event should be broadcast synchronously.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldBroadcastNow()
|
||||
{
|
||||
return $this->event === 'deleted' &&
|
||||
! method_exists($this->model, 'bootSoftDeletes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function event()
|
||||
{
|
||||
return $this->event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait BroadcastsEvents
|
||||
{
|
||||
/**
|
||||
* Boot the event broadcasting trait.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootBroadcastsEvents()
|
||||
{
|
||||
static::created(function ($model) {
|
||||
$model->broadcastCreated();
|
||||
});
|
||||
|
||||
static::updated(function ($model) {
|
||||
$model->broadcastUpdated();
|
||||
});
|
||||
|
||||
if (method_exists(static::class, 'bootSoftDeletes')) {
|
||||
static::softDeleted(function ($model) {
|
||||
$model->broadcastTrashed();
|
||||
});
|
||||
|
||||
static::restored(function ($model) {
|
||||
$model->broadcastRestored();
|
||||
});
|
||||
}
|
||||
|
||||
static::deleted(function ($model) {
|
||||
$model->broadcastDeleted();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was created.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastCreated($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('created'), 'created', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was updated.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastUpdated($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('updated'), 'updated', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was trashed.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastTrashed($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('trashed'), 'trashed', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was restored.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastRestored($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('restored'), 'restored', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast that the model was deleted.
|
||||
*
|
||||
* @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast
|
||||
*/
|
||||
public function broadcastDeleted($channels = null)
|
||||
{
|
||||
return $this->broadcastIfBroadcastChannelsExistForEvent(
|
||||
$this->newBroadcastableModelEvent('deleted'), 'deleted', $channels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the given event instance if channels are configured for the model event.
|
||||
*
|
||||
* @param mixed $instance
|
||||
* @param string $event
|
||||
* @param mixed $channels
|
||||
* @return \Illuminate\Broadcasting\PendingBroadcast|null
|
||||
*/
|
||||
protected function broadcastIfBroadcastChannelsExistForEvent($instance, $event, $channels = null)
|
||||
{
|
||||
if (! static::$isBroadcasting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! empty($this->broadcastOn($event)) || ! empty($channels)) {
|
||||
return broadcast($instance->onChannels(Arr::wrap($channels)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new broadcastable model event event.
|
||||
*
|
||||
* @param string $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function newBroadcastableModelEvent($event)
|
||||
{
|
||||
return tap($this->newBroadcastableEvent($event), function ($event) {
|
||||
$event->connection = property_exists($this, 'broadcastConnection')
|
||||
? $this->broadcastConnection
|
||||
: $this->broadcastConnection();
|
||||
|
||||
$event->queue = property_exists($this, 'broadcastQueue')
|
||||
? $this->broadcastQueue
|
||||
: $this->broadcastQueue();
|
||||
|
||||
$event->afterCommit = property_exists($this, 'broadcastAfterCommit')
|
||||
? $this->broadcastAfterCommit
|
||||
: $this->broadcastAfterCommit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new broadcastable model event for the model.
|
||||
*
|
||||
* @param string $event
|
||||
* @return \Illuminate\Database\Eloquent\BroadcastableModelEventOccurred
|
||||
*/
|
||||
protected function newBroadcastableEvent($event)
|
||||
{
|
||||
return new BroadcastableModelEventOccurred($this, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels that model events should broadcast on.
|
||||
*
|
||||
* @param string $event
|
||||
* @return \Illuminate\Broadcasting\Channel|array
|
||||
*/
|
||||
public function broadcastOn($event)
|
||||
{
|
||||
return [$this];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue connection that should be used to broadcast model events.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function broadcastConnection()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue that should be used to broadcast model events.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function broadcastQueue()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model event broadcast queued job should be dispatched after all transactions are committed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function broadcastAfterCommit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+1724
@@ -0,0 +1,1724 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Database\Concerns\BuildsQueries;
|
||||
use Illuminate\Database\Concerns\ExplainsQueries;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\RecordsNotFoundException;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* @property-read HigherOrderBuilderProxy $orWhere
|
||||
*
|
||||
* @mixin \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
class Builder
|
||||
{
|
||||
use Concerns\QueriesRelationships, ExplainsQueries, ForwardsCalls;
|
||||
use BuildsQueries {
|
||||
sole as baseSole;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base query builder instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* The model being queried.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* The relationships that should be eager loaded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $eagerLoad = [];
|
||||
|
||||
/**
|
||||
* All of the globally registered builder macros.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $macros = [];
|
||||
|
||||
/**
|
||||
* All of the locally registered builder macros.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $localMacros = [];
|
||||
|
||||
/**
|
||||
* A replacement for the typical delete function.
|
||||
*
|
||||
* @var \Closure
|
||||
*/
|
||||
protected $onDelete;
|
||||
|
||||
/**
|
||||
* The methods that should be returned from query builder.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $passthru = [
|
||||
'average',
|
||||
'avg',
|
||||
'count',
|
||||
'dd',
|
||||
'doesntExist',
|
||||
'dump',
|
||||
'exists',
|
||||
'getBindings',
|
||||
'getConnection',
|
||||
'getGrammar',
|
||||
'insert',
|
||||
'insertGetId',
|
||||
'insertOrIgnore',
|
||||
'insertUsing',
|
||||
'max',
|
||||
'min',
|
||||
'raw',
|
||||
'sum',
|
||||
'toSql',
|
||||
];
|
||||
|
||||
/**
|
||||
* Applied global scopes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $scopes = [];
|
||||
|
||||
/**
|
||||
* Removed global scopes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $removedScopes = [];
|
||||
|
||||
/**
|
||||
* 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 \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function make(array $attributes = [])
|
||||
{
|
||||
return $this->newModelInstance($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new global scope.
|
||||
*
|
||||
* @param string $identifier
|
||||
* @param \Illuminate\Database\Eloquent\Scope|\Closure $scope
|
||||
* @return $this
|
||||
*/
|
||||
public function withGlobalScope($identifier, $scope)
|
||||
{
|
||||
$this->scopes[$identifier] = $scope;
|
||||
|
||||
if (method_exists($scope, 'extend')) {
|
||||
$scope->extend($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a registered global scope.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|string $scope
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutGlobalScope($scope)
|
||||
{
|
||||
if (! is_string($scope)) {
|
||||
$scope = get_class($scope);
|
||||
}
|
||||
|
||||
unset($this->scopes[$scope]);
|
||||
|
||||
$this->removedScopes[] = $scope;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all or passed registered global scopes.
|
||||
*
|
||||
* @param array|null $scopes
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutGlobalScopes(array $scopes = null)
|
||||
{
|
||||
if (! is_array($scopes)) {
|
||||
$scopes = array_keys($this->scopes);
|
||||
}
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
$this->withoutGlobalScope($scope);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of global scopes that were removed from the query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function removedScopes()
|
||||
{
|
||||
return $this->removedScopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where clause on the primary key to the query.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return $this
|
||||
*/
|
||||
public function whereKey($id)
|
||||
{
|
||||
if ($id instanceof Model) {
|
||||
$id = $id->getKey();
|
||||
}
|
||||
|
||||
if (is_array($id) || $id instanceof Arrayable) {
|
||||
$this->query->whereIn($this->model->getQualifiedKeyName(), $id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($id !== null && $this->model->getKeyType() === 'string') {
|
||||
$id = (string) $id;
|
||||
}
|
||||
|
||||
return $this->where($this->model->getQualifiedKeyName(), '=', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a where clause on the primary key to the query.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return $this
|
||||
*/
|
||||
public function whereKeyNot($id)
|
||||
{
|
||||
if ($id instanceof Model) {
|
||||
$id = $id->getKey();
|
||||
}
|
||||
|
||||
if (is_array($id) || $id instanceof Arrayable) {
|
||||
$this->query->whereNotIn($this->model->getQualifiedKeyName(), $id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($id !== null && $this->model->getKeyType() === 'string') {
|
||||
$id = (string) $id;
|
||||
}
|
||||
|
||||
return $this->where($this->model->getQualifiedKeyName(), '!=', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic where clause to the query.
|
||||
*
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function where($column, $operator = null, $value = null, $boolean = 'and')
|
||||
{
|
||||
if ($column instanceof Closure && is_null($operator)) {
|
||||
$column($query = $this->model->newQueryWithoutRelationships());
|
||||
|
||||
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
|
||||
} else {
|
||||
$this->query->where(...func_get_args());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic where clause to the query, and return the first result.
|
||||
*
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @param string $boolean
|
||||
* @return \Illuminate\Database\Eloquent\Model|static|null
|
||||
*/
|
||||
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
|
||||
{
|
||||
return $this->where($column, $operator, $value, $boolean)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "or where" clause to the query.
|
||||
*
|
||||
* @param \Closure|array|string|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWhere($column, $operator = null, $value = null)
|
||||
{
|
||||
[$value, $operator] = $this->query->prepareValueAndOperator(
|
||||
$value, $operator, func_num_args() === 2
|
||||
);
|
||||
|
||||
return $this->where($column, $operator, $value, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "order by" clause for a timestamp to the query.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @return $this
|
||||
*/
|
||||
public function latest($column = null)
|
||||
{
|
||||
if (is_null($column)) {
|
||||
$column = $this->model->getCreatedAtColumn() ?? 'created_at';
|
||||
}
|
||||
|
||||
$this->query->latest($column);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "order by" clause for a timestamp to the query.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @return $this
|
||||
*/
|
||||
public function oldest($column = null)
|
||||
{
|
||||
if (is_null($column)) {
|
||||
$column = $this->model->getCreatedAtColumn() ?? 'created_at';
|
||||
}
|
||||
|
||||
$this->query->oldest($column);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models from plain arrays.
|
||||
*
|
||||
* @param array $items
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function hydrate(array $items)
|
||||
{
|
||||
$instance = $this->newModelInstance();
|
||||
|
||||
return $instance->newCollection(array_map(function ($item) use ($items, $instance) {
|
||||
$model = $instance->newFromBuilder($item);
|
||||
|
||||
if (count($items) > 1) {
|
||||
$model->preventsLazyLoading = Model::preventsLazyLoading();
|
||||
}
|
||||
|
||||
return $model;
|
||||
}, $items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models from a raw query.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function fromQuery($query, $bindings = [])
|
||||
{
|
||||
return $this->hydrate(
|
||||
$this->query->getConnection()->select($query, $bindings)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its primary key.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null
|
||||
*/
|
||||
public function find($id, $columns = ['*'])
|
||||
{
|
||||
if (is_array($id) || $id instanceof Arrayable) {
|
||||
return $this->findMany($id, $columns);
|
||||
}
|
||||
|
||||
return $this->whereKey($id)->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple models by their primary keys.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Support\Arrayable|array $ids
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function findMany($ids, $columns = ['*'])
|
||||
{
|
||||
$ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
|
||||
|
||||
if (empty($ids)) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
return $this->whereKey($ids)->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its primary key or throw an exception.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[]
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function findOrFail($id, $columns = ['*'])
|
||||
{
|
||||
$result = $this->find($id, $columns);
|
||||
|
||||
$id = $id instanceof Arrayable ? $id->toArray() : $id;
|
||||
|
||||
if (is_array($id)) {
|
||||
if (count($result) === count(array_unique($id))) {
|
||||
return $result;
|
||||
}
|
||||
} elseif (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(
|
||||
get_class($this->model), $id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its primary key or return fresh model instance.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function findOrNew($id, $columns = ['*'])
|
||||
{
|
||||
if (! is_null($model = $this->find($id, $columns))) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
return $this->newModelInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first record matching the attributes or instantiate it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
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 or create it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function firstOrCreate(array $attributes = [], array $values = [])
|
||||
{
|
||||
if (! is_null($instance = $this->where($attributes)->first())) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
return tap($this->newModelInstance(array_merge($attributes, $values)), function ($instance) {
|
||||
$instance->save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a record matching the attributes, and fill it with values.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function updateOrCreate(array $attributes, array $values = [])
|
||||
{
|
||||
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
|
||||
$instance->fill($values)->save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result or throw an exception.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function firstOrFail($columns = ['*'])
|
||||
{
|
||||
if (! is_null($model = $this->first($columns))) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->model));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result or call a callback.
|
||||
*
|
||||
* @param \Closure|array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Model|static|mixed
|
||||
*/
|
||||
public function firstOr($columns = ['*'], Closure $callback = null)
|
||||
{
|
||||
if ($columns instanceof Closure) {
|
||||
$callback = $columns;
|
||||
|
||||
$columns = ['*'];
|
||||
}
|
||||
|
||||
if (! is_null($model = $this->first($columns))) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
return $callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result if it's the sole matching record.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @throws \Illuminate\Database\MultipleRecordsFoundException
|
||||
*/
|
||||
public function sole($columns = ['*'])
|
||||
{
|
||||
try {
|
||||
return $this->baseSole($columns);
|
||||
} catch (RecordsNotFoundException $exception) {
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->model));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single column's value from the first result of a query.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @return mixed
|
||||
*/
|
||||
public function value($column)
|
||||
{
|
||||
if ($result = $this->first([$column])) {
|
||||
return $result->{Str::afterLast($column, '.')};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single column's value from the first result of the query or throw an exception.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function valueOrFail($column)
|
||||
{
|
||||
return $this->firstOrFail([$column])->{Str::afterLast($column, '.')};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query as a "select" statement.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection|static[]
|
||||
*/
|
||||
public function get($columns = ['*'])
|
||||
{
|
||||
$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 $builder->getModel()->newCollection($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hydrated models without eager loading.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model[]|static[]
|
||||
*/
|
||||
public function getModels($columns = ['*'])
|
||||
{
|
||||
return $this->model->hydrate(
|
||||
$this->query->get($columns)->all()
|
||||
)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load the relationships for the models.
|
||||
*
|
||||
* @param array $models
|
||||
* @return array
|
||||
*/
|
||||
public function eagerLoadRelations(array $models)
|
||||
{
|
||||
foreach ($this->eagerLoad as $name => $constraints) {
|
||||
// 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 (strpos($name, '.') === false) {
|
||||
$models = $this->eagerLoadRelation($models, $name, $constraints);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly load the relationship on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $name
|
||||
* @param \Closure $constraints
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerLoadRelation(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
|
||||
// query back to it in order that any where conditions might be specified.
|
||||
$relation = $this->getRelation($name);
|
||||
|
||||
$relation->addEagerConstraints($models);
|
||||
|
||||
$constraints($relation);
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relation instance for the given relation name.
|
||||
*
|
||||
* @param string $name
|
||||
* @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.
|
||||
$relation = Relation::noConstraints(function () use ($name) {
|
||||
try {
|
||||
return $this->getModel()->newInstance()->$name();
|
||||
} catch (BadMethodCallException $e) {
|
||||
throw RelationNotFoundException::make($this->getModel(), $name);
|
||||
}
|
||||
});
|
||||
|
||||
$nested = $this->relationsNestedUnder($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
|
||||
// is loaded. In this way they will all trickle down as they are loaded.
|
||||
if (count($nested) > 0) {
|
||||
$relation->getQuery()->with($nested);
|
||||
}
|
||||
|
||||
return $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deeply nested relations for a given top-level relation.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
protected function relationsNestedUnder($relation)
|
||||
{
|
||||
$nested = [];
|
||||
|
||||
// We are basically looking for any relationships that are nested deeper than
|
||||
// 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)) {
|
||||
$nested[substr($name, strlen($relation.'.'))] = $constraints;
|
||||
}
|
||||
}
|
||||
|
||||
return $nested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the relationship is nested.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
protected function isNestedUnder($relation, $name)
|
||||
{
|
||||
return Str::contains($name, '.') && Str::startsWith($name, $relation.'.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lazy collection for the given query.
|
||||
*
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function cursor()
|
||||
{
|
||||
return $this->applyScopes()->query->cursor()->map(function ($record) {
|
||||
return $this->newModelInstance()->newFromBuilder($record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generic "order by" clause if the query doesn't already have one.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enforceOrderBy()
|
||||
{
|
||||
if (empty($this->query->orders) && empty($this->query->unionOrders)) {
|
||||
$this->orderBy($this->model->getQualifiedKeyName(), 'asc');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array with the values of a given column.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @param string|null $key
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function pluck($column, $key = null)
|
||||
{
|
||||
$results = $this->toBase()->pluck($column, $key);
|
||||
|
||||
// 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) &&
|
||||
! $this->model->hasCast($column) &&
|
||||
! in_array($column, $this->model->getDates())) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
return $results->map(function ($value) use ($column) {
|
||||
return $this->model->newFromBuilder([$column => $value])->{$column};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$page = $page ?: Paginator::resolveCurrentPage($pageName);
|
||||
|
||||
$perPage = $perPage ?: $this->model->getPerPage();
|
||||
|
||||
$results = ($total = $this->toBase()->getCountForPagination())
|
||||
? $this->forPage($page, $perPage)->get($columns)
|
||||
: $this->model->newCollection();
|
||||
|
||||
return $this->paginator($results, $total, $perPage, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => $pageName,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query into a simple paginator.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
* @return \Illuminate\Contracts\Pagination\Paginator
|
||||
*/
|
||||
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$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->skip(($page - 1) * $perPage)->take($perPage + 1);
|
||||
|
||||
return $this->simplePaginator($this->get($columns), $perPage, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => $pageName,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query into a cursor paginator.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $cursorName
|
||||
* @param \Illuminate\Pagination\Cursor|string|null $cursor
|
||||
* @return \Illuminate\Contracts\Pagination\CursorPaginator
|
||||
*/
|
||||
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
|
||||
{
|
||||
$perPage = $perPage ?: $this->model->getPerPage();
|
||||
|
||||
return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the proper order by required for cursor pagination.
|
||||
*
|
||||
* @param bool $shouldReverse
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function ensureOrderForCursorPagination($shouldReverse = false)
|
||||
{
|
||||
$orders = collect($this->query->orders);
|
||||
|
||||
if ($orders->count() === 0) {
|
||||
$this->enforceOrderBy();
|
||||
}
|
||||
|
||||
if ($shouldReverse) {
|
||||
$this->query->orders = collect($this->query->orders)->map(function ($order) {
|
||||
$order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
return $order;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
return collect($this->query->orders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new model and return the instance.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|$this
|
||||
*/
|
||||
public function create(array $attributes = [])
|
||||
{
|
||||
return tap($this->newModelInstance($attributes), function ($instance) {
|
||||
$instance->save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new model and return the instance. Allow mass-assignment.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|$this
|
||||
*/
|
||||
public function forceCreate(array $attributes)
|
||||
{
|
||||
return $this->model->unguarded(function () use ($attributes) {
|
||||
return $this->newModelInstance()->create($attributes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update records in the database.
|
||||
*
|
||||
* @param array $values
|
||||
* @return int
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
return $this->toBase()->update($this->addUpdatedAtColumn($values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new records or update the existing ones.
|
||||
*
|
||||
* @param array $values
|
||||
* @param array|string $uniqueBy
|
||||
* @param array|null $update
|
||||
* @return int
|
||||
*/
|
||||
public function upsert(array $values, $uniqueBy, $update = null)
|
||||
{
|
||||
if (empty($values)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (! is_array(reset($values))) {
|
||||
$values = [$values];
|
||||
}
|
||||
|
||||
if (is_null($update)) {
|
||||
$update = array_keys(reset($values));
|
||||
}
|
||||
|
||||
return $this->toBase()->upsert(
|
||||
$this->addTimestampsToUpsertValues($values),
|
||||
$uniqueBy,
|
||||
$this->addUpdatedAtToUpsertColumns($update)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a column's value by a given amount.
|
||||
*
|
||||
* @param string|\Illuminate\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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement a column's value by a given amount.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @param float|int $amount
|
||||
* @param array $extra
|
||||
* @return int
|
||||
*/
|
||||
public function decrement($column, $amount = 1, array $extra = [])
|
||||
{
|
||||
return $this->toBase()->decrement(
|
||||
$column, $amount, $this->addUpdatedAtColumn($extra)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "updated at" column to an array of values.
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
protected function addUpdatedAtColumn(array $values)
|
||||
{
|
||||
if (! $this->model->usesTimestamps() ||
|
||||
is_null($this->model->getUpdatedAtColumn())) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
$column = $this->model->getUpdatedAtColumn();
|
||||
|
||||
$values = array_merge(
|
||||
[$column => $this->model->freshTimestampString()],
|
||||
$values
|
||||
);
|
||||
|
||||
$segments = preg_split('/\s+as\s+/i', $this->query->from);
|
||||
|
||||
$qualifiedColumn = end($segments).'.'.$column;
|
||||
|
||||
$values[$qualifiedColumn] = $values[$column];
|
||||
|
||||
unset($values[$column]);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add timestamps to the inserted values.
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
protected function addTimestampsToUpsertValues(array $values)
|
||||
{
|
||||
if (! $this->model->usesTimestamps()) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
$timestamp = $this->model->freshTimestampString();
|
||||
|
||||
$columns = array_filter([
|
||||
$this->model->getCreatedAtColumn(),
|
||||
$this->model->getUpdatedAtColumn(),
|
||||
]);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
foreach ($values as &$row) {
|
||||
$row = array_merge([$column => $timestamp], $row);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "updated at" column to the updated columns.
|
||||
*
|
||||
* @param array $update
|
||||
* @return array
|
||||
*/
|
||||
protected function addUpdatedAtToUpsertColumns(array $update)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete records from the database.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if (isset($this->onDelete)) {
|
||||
return call_user_func($this->onDelete, $this);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given model has a scope.
|
||||
*
|
||||
* @param string $scope
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNamedScope($scope)
|
||||
{
|
||||
return $this->model && $this->model->hasNamedScope($scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the given local model scopes.
|
||||
*
|
||||
* @param array|string $scopes
|
||||
* @return static|mixed
|
||||
*/
|
||||
public function scopes($scopes)
|
||||
{
|
||||
$builder = $this;
|
||||
|
||||
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, []];
|
||||
}
|
||||
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the scopes to the Eloquent builder instance and return it.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function applyScopes()
|
||||
{
|
||||
if (! $this->scopes) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$builder = clone $this;
|
||||
|
||||
foreach ($this->scopes as $identifier => $scope) {
|
||||
if (! isset($builder->scopes[$identifier])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given scope on the current builder instance.
|
||||
*
|
||||
* @param callable $scope
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
protected function callScope(callable $scope, array $parameters = [])
|
||||
{
|
||||
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(...array_values($parameters)) ?? $this;
|
||||
|
||||
if (count((array) $query->wheres) > $originalWhereCount) {
|
||||
$this->addNewWheresWithinGroup($query, $originalWhereCount);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given named scope on the current builder instance.
|
||||
*
|
||||
* @param string $scope
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
protected function callNamedScope($scope, array $parameters = [])
|
||||
{
|
||||
return $this->callScope(function (...$parameters) use ($scope) {
|
||||
return $this->model->callNamedScope($scope, $parameters);
|
||||
}, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest where conditions by slicing them at the given where count.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @param int $originalWhereCount
|
||||
* @return void
|
||||
*/
|
||||
protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCount)
|
||||
{
|
||||
// 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
|
||||
// their own sections. This is to prevent any confusing logic order.
|
||||
$allWheres = $query->wheres;
|
||||
|
||||
$query->wheres = [];
|
||||
|
||||
$this->groupWhereSliceForScope(
|
||||
$query, array_slice($allWheres, 0, $originalWhereCount)
|
||||
);
|
||||
|
||||
$this->groupWhereSliceForScope(
|
||||
$query, array_slice($allWheres, $originalWhereCount)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice where conditions at the given offset and add them to the query as a nested condition.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @param array $whereSlice
|
||||
* @return void
|
||||
*/
|
||||
protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice)
|
||||
{
|
||||
$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('or')) {
|
||||
$query->wheres[] = $this->createNestedWhere(
|
||||
$whereSlice, $whereBooleans->first()
|
||||
);
|
||||
} else {
|
||||
$query->wheres = array_merge($query->wheres, $whereSlice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a where array with nested where conditions.
|
||||
*
|
||||
* @param array $whereSlice
|
||||
* @param string $boolean
|
||||
* @return array
|
||||
*/
|
||||
protected function createNestedWhere($whereSlice, $boolean = 'and')
|
||||
{
|
||||
$whereGroup = $this->getQuery()->forNestedWhere();
|
||||
|
||||
$whereGroup->wheres = $whereSlice;
|
||||
|
||||
return ['type' => 'Nested', 'query' => $whereGroup, 'boolean' => $boolean];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relationships that should be eager loaded.
|
||||
*
|
||||
* @param string|array $relations
|
||||
* @param string|\Closure|null $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function with($relations, $callback = null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the specified relations from being eager loaded.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function without($relations)
|
||||
{
|
||||
$this->eagerLoad = array_diff_key($this->eagerLoad, array_flip(
|
||||
is_string($relations) ? func_get_args() : $relations
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relationships that should be eager loaded while removing any previously added eager loading specifications.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function withOnly($relations)
|
||||
{
|
||||
$this->eagerLoad = [];
|
||||
|
||||
return $this->with($relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the model being queried.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function newModelInstance($attributes = [])
|
||||
{
|
||||
return $this->model->newInstance($attributes)->setConnection(
|
||||
$this->query->getConnection()->getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a list of relations into individuals.
|
||||
*
|
||||
* @param array $relations
|
||||
* @return array
|
||||
*/
|
||||
protected function parseWithRelations(array $relations)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($relations as $name => $constraints) {
|
||||
// If the "name" value is a numeric key, we can assume that no constraints
|
||||
// have been specified. We will just put an empty Closure there so that
|
||||
// we can treat these all the same while we are looping through them.
|
||||
if (is_numeric($name)) {
|
||||
$name = $constraints;
|
||||
|
||||
[$name, $constraints] = Str::contains($name, ':')
|
||||
? $this->createSelectWithConstraint($name)
|
||||
: [$name, static function () {
|
||||
//
|
||||
}];
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (Str::contains($column, '.')) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
return $query instanceof BelongsToMany
|
||||
? $query->getRelated()->getTable().'.'.$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply query-time casts to the model instance.
|
||||
*
|
||||
* @param array $casts
|
||||
* @return $this
|
||||
*/
|
||||
public function withCasts($casts)
|
||||
{
|
||||
$this->model->mergeCasts($casts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default key name of the table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function defaultKeyName()
|
||||
{
|
||||
return $this->getModel()->getKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model instance being queried.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a model instance for the model being queried.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return $this
|
||||
*/
|
||||
public function setModel(Model $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
|
||||
$this->query->from($model->getTable());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify the given column name by the model's table.
|
||||
*
|
||||
* @param string|\Illuminate\Database\Query\Expression $column
|
||||
* @return string
|
||||
*/
|
||||
public function qualifyColumn($column)
|
||||
{
|
||||
return $this->model->qualifyColumn($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify the given columns with the model's table.
|
||||
*
|
||||
* @param array|\Illuminate\Database\Query\Expression $columns
|
||||
* @return array
|
||||
*/
|
||||
public function qualifyColumns($columns)
|
||||
{
|
||||
return $this->model->qualifyColumns($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the given macro by name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \Closure
|
||||
*/
|
||||
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 ($key === 'orWhere') {
|
||||
return new HigherOrderBuilderProxy($this, $key);
|
||||
}
|
||||
|
||||
throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically handle calls into the query instance.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if ($method === 'macro') {
|
||||
$this->localMacros[$parameters[0]] = $parameters[1];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->hasMacro($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);
|
||||
}
|
||||
|
||||
if ($this->hasNamedScope($method)) {
|
||||
return $this->callNamedScope($method, $parameters);
|
||||
}
|
||||
|
||||
if (in_array($method, $this->passthru)) {
|
||||
return $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)) {
|
||||
$method->setAccessible(true);
|
||||
|
||||
static::macro($method->name, $method->invoke($mixin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the Eloquent query builder.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function clone()
|
||||
{
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a clone of the underlying query builder when cloning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->query = clone $this->query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use ArrayObject as BaseArrayObject;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use JsonSerializable;
|
||||
|
||||
class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Get a collection containing the underlying array.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
return collect($this->getArrayCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->getArrayCopy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array that should be JSON serialized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->getArrayCopy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
|
||||
class AsArrayObject implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
return isset($attributes[$key]) ? new ArrayObject(json_decode($attributes[$key], true)) : 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AsCollection implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
return isset($attributes[$key]) ? new Collection(json_decode($attributes[$key], true)) : null;
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return [$key => json_encode($value)];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class AsEncryptedArrayObject implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
return new ArrayObject(json_decode(Crypt::decryptString($attributes[$key]), true));
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return [$key => Crypt::encryptString(json_encode($value))];
|
||||
}
|
||||
|
||||
public function serialize($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return $value->getArrayCopy();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class AsEncryptedCollection implements Castable
|
||||
{
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return object|string
|
||||
*/
|
||||
public static function castUsing(array $arguments)
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
public function get($model, $key, $value, $attributes)
|
||||
{
|
||||
return new Collection(json_decode(Crypt::decryptString($attributes[$key]), true));
|
||||
}
|
||||
|
||||
public function set($model, $key, $value, $attributes)
|
||||
{
|
||||
return [$key => Crypt::encryptString(json_encode($value))];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+748
@@ -0,0 +1,748 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Contracts\Queue\QueueableCollection;
|
||||
use Illuminate\Contracts\Queue\QueueableEntity;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
use Illuminate\Support\Str;
|
||||
use LogicException;
|
||||
|
||||
class Collection extends BaseCollection implements QueueableCollection
|
||||
{
|
||||
/**
|
||||
* Find a model in the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $default
|
||||
* @return \Illuminate\Database\Eloquent\Model|static|null
|
||||
*/
|
||||
public function find($key, $default = null)
|
||||
{
|
||||
if ($key instanceof Model) {
|
||||
$key = $key->getKey();
|
||||
}
|
||||
|
||||
if ($key instanceof Arrayable) {
|
||||
$key = $key->toArray();
|
||||
}
|
||||
|
||||
if (is_array($key)) {
|
||||
if ($this->isEmpty()) {
|
||||
return new static;
|
||||
}
|
||||
|
||||
return $this->whereIn($this->first()->getKeyName(), $key);
|
||||
}
|
||||
|
||||
return Arr::first($this->items, function ($model) use ($key) {
|
||||
return $model->getKey() == $key;
|
||||
}, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationships onto the collection.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function load($relations)
|
||||
{
|
||||
if ($this->isNotEmpty()) {
|
||||
if (is_string($relations)) {
|
||||
$relations = func_get_args();
|
||||
}
|
||||
|
||||
$query = $this->first()->newQueryWithoutRelationships()->with($relations);
|
||||
|
||||
$this->items = $query->eagerLoadRelations($this->items);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of aggregations over relationship's column onto the collection.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @param string $column
|
||||
* @param string $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);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship counts onto the collection.
|
||||
*
|
||||
* @param array|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|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|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|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|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|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|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMissing($relations)
|
||||
{
|
||||
if (is_string($relations)) {
|
||||
$relations = func_get_args();
|
||||
}
|
||||
|
||||
foreach ($relations as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
$key = $value;
|
||||
}
|
||||
|
||||
$segments = explode('.', explode(':', $key)[0]);
|
||||
|
||||
if (Str::contains($key, ':')) {
|
||||
$segments[count($segments) - 1] .= ':'.explode(':', $key)[1];
|
||||
}
|
||||
|
||||
$path = [];
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
$path[] = [$segment => $segment];
|
||||
}
|
||||
|
||||
if (is_callable($value)) {
|
||||
$path[count($segments) - 1][end($segments)] = $value;
|
||||
}
|
||||
|
||||
$this->loadMissingRelation($this, $path);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a relationship path if it is not already eager loaded.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $models
|
||||
* @param array $path
|
||||
* @return void
|
||||
*/
|
||||
protected function loadMissingRelation(self $models, array $path)
|
||||
{
|
||||
$relation = array_shift($path);
|
||||
|
||||
$name = explode(':', key($relation))[0];
|
||||
|
||||
if (is_string(reset($relation))) {
|
||||
$relation = reset($relation);
|
||||
}
|
||||
|
||||
$models->filter(function ($model) use ($name) {
|
||||
return ! is_null($model) && ! $model->relationLoaded($name);
|
||||
})->load($relation);
|
||||
|
||||
if (empty($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$models = $models->pluck($name)->whereNotNull();
|
||||
|
||||
if ($models->first() instanceof BaseCollection) {
|
||||
$models = $models->collapse();
|
||||
}
|
||||
|
||||
$this->loadMissingRelation(new static($models), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationships onto the mixed relationship collection.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorph($relation, $relations)
|
||||
{
|
||||
$this->pluck($relation)
|
||||
->filter()
|
||||
->groupBy(function ($model) {
|
||||
return get_class($model);
|
||||
})
|
||||
->each(function ($models, $className) use ($relations) {
|
||||
static::make($models)->load($relations[$className] ?? []);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of relationship counts onto the mixed relationship collection.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphCount($relation, $relations)
|
||||
{
|
||||
$this->pluck($relation)
|
||||
->filter()
|
||||
->groupBy(function ($model) {
|
||||
return get_class($model);
|
||||
})
|
||||
->each(function ($models, $className) use ($relations) {
|
||||
static::make($models)->loadCount($relations[$className] ?? []);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a key exists in the collection.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function contains($key, $operator = null, $value = null)
|
||||
{
|
||||
if (func_num_args() > 1 || $this->useAsCallable($key)) {
|
||||
return parent::contains(...func_get_args());
|
||||
}
|
||||
|
||||
if ($key instanceof Model) {
|
||||
return parent::contains(function ($model) use ($key) {
|
||||
return $model->is($key);
|
||||
});
|
||||
}
|
||||
|
||||
return parent::contains(function ($model) use ($key) {
|
||||
return $model->getKey() == $key;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of primary keys.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function modelKeys()
|
||||
{
|
||||
return array_map(function ($model) {
|
||||
return $model->getKey();
|
||||
}, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the collection with the given items.
|
||||
*
|
||||
* @param \ArrayAccess|array $items
|
||||
* @return static
|
||||
*/
|
||||
public function merge($items)
|
||||
{
|
||||
$dictionary = $this->getDictionary();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$dictionary[$item->getKey()] = $item;
|
||||
}
|
||||
|
||||
return new static(array_values($dictionary));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a map over each of the items.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return \Illuminate\Support\Collection|static
|
||||
*/
|
||||
public function map(callable $callback)
|
||||
{
|
||||
$result = parent::map($callback);
|
||||
|
||||
return $result->contains(function ($item) {
|
||||
return ! $item instanceof Model;
|
||||
}) ? $result->toBase() : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an associative map over each of the items.
|
||||
*
|
||||
* The callback should return an associative array with a single key / value pair.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return \Illuminate\Support\Collection|static
|
||||
*/
|
||||
public function mapWithKeys(callable $callback)
|
||||
{
|
||||
$result = parent::mapWithKeys($callback);
|
||||
|
||||
return $result->contains(function ($item) {
|
||||
return ! $item instanceof Model;
|
||||
}) ? $result->toBase() : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload a fresh model instance from the database for all the entities.
|
||||
*
|
||||
* @param array|string $with
|
||||
* @return static
|
||||
*/
|
||||
public function fresh($with = [])
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return new static;
|
||||
}
|
||||
|
||||
$model = $this->first();
|
||||
|
||||
$freshModels = $model->newQueryWithoutScopes()
|
||||
->with(is_string($with) ? func_get_args() : $with)
|
||||
->whereIn($model->getKeyName(), $this->modelKeys())
|
||||
->get()
|
||||
->getDictionary();
|
||||
|
||||
return $this->filter(function ($model) use ($freshModels) {
|
||||
return $model->exists && isset($freshModels[$model->getKey()]);
|
||||
})
|
||||
->map(function ($model) use ($freshModels) {
|
||||
return $freshModels[$model->getKey()];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff the collection with the given items.
|
||||
*
|
||||
* @param \ArrayAccess|array $items
|
||||
* @return static
|
||||
*/
|
||||
public function diff($items)
|
||||
{
|
||||
$diff = new static;
|
||||
|
||||
$dictionary = $this->getDictionary($items);
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
if (! isset($dictionary[$item->getKey()])) {
|
||||
$diff->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect the collection with the given items.
|
||||
*
|
||||
* @param \ArrayAccess|array $items
|
||||
* @return static
|
||||
*/
|
||||
public function intersect($items)
|
||||
{
|
||||
$intersect = new static;
|
||||
|
||||
if (empty($items)) {
|
||||
return $intersect;
|
||||
}
|
||||
|
||||
$dictionary = $this->getDictionary($items);
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
if (isset($dictionary[$item->getKey()])) {
|
||||
$intersect->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $intersect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only unique items from the collection.
|
||||
*
|
||||
* @param string|callable|null $key
|
||||
* @param bool $strict
|
||||
* @return static
|
||||
*/
|
||||
public function unique($key = null, $strict = false)
|
||||
{
|
||||
if (! is_null($key)) {
|
||||
return parent::unique($key, $strict);
|
||||
}
|
||||
|
||||
return new static(array_values($this->getDictionary()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the models from the collection with the specified keys.
|
||||
*
|
||||
* @param mixed $keys
|
||||
* @return static
|
||||
*/
|
||||
public function only($keys)
|
||||
{
|
||||
if (is_null($keys)) {
|
||||
return new static($this->items);
|
||||
}
|
||||
|
||||
$dictionary = Arr::only($this->getDictionary(), $keys);
|
||||
|
||||
return new static(array_values($dictionary));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all models in the collection except the models with specified keys.
|
||||
*
|
||||
* @param mixed $keys
|
||||
* @return static
|
||||
*/
|
||||
public function except($keys)
|
||||
{
|
||||
$dictionary = Arr::except($this->getDictionary(), $keys);
|
||||
|
||||
return new static(array_values($dictionary));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden across the entire collection.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHidden($attributes)
|
||||
{
|
||||
return $this->each->makeHidden($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible across the entire collection.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisible($attributes)
|
||||
{
|
||||
return $this->each->makeVisible($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an attribute across the entire collection.
|
||||
*
|
||||
* @param array|string $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function append($attributes)
|
||||
{
|
||||
return $this->each->append($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dictionary keyed by primary keys.
|
||||
*
|
||||
* @param \ArrayAccess|array|null $items
|
||||
* @return array
|
||||
*/
|
||||
public function getDictionary($items = null)
|
||||
{
|
||||
$items = is_null($items) ? $this->items : $items;
|
||||
|
||||
$dictionary = [];
|
||||
|
||||
foreach ($items as $value) {
|
||||
$dictionary[$value->getKey()] = $value;
|
||||
}
|
||||
|
||||
return $dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following methods are intercepted to always return base collections.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get an array with the values of a given key.
|
||||
*
|
||||
* @param string|array $value
|
||||
* @param string|null $key
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function pluck($value, $key = null)
|
||||
{
|
||||
return $this->toBase()->pluck($value, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the keys of the collection items.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function keys()
|
||||
{
|
||||
return $this->toBase()->keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zip the collection together with one or more arrays.
|
||||
*
|
||||
* @param mixed ...$items
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function zip($items)
|
||||
{
|
||||
return $this->toBase()->zip(...func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the collection of items into a single array.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collapse()
|
||||
{
|
||||
return $this->toBase()->collapse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flattened array of the items in the collection.
|
||||
*
|
||||
* @param int $depth
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function flatten($depth = INF)
|
||||
{
|
||||
return $this->toBase()->flatten($depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip the items in the collection.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function flip()
|
||||
{
|
||||
return $this->toBase()->flip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad collection to the specified length with a value.
|
||||
*
|
||||
* @param int $size
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function pad($size, $value)
|
||||
{
|
||||
return $this->toBase()->pad($size, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comparison function to detect duplicates.
|
||||
*
|
||||
* @param bool $strict
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function duplicateComparator($strict)
|
||||
{
|
||||
return function ($a, $b) {
|
||||
return $a->is($b);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the entities being queued.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function getQueueableClass()
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = get_class($this->first());
|
||||
|
||||
$this->each(function ($model) use ($class) {
|
||||
if (get_class($model) !== $class) {
|
||||
throw new LogicException('Queueing collections with multiple model types is not supported.');
|
||||
}
|
||||
});
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifiers for all of the entities.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueueableIds()
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->first() instanceof QueueableEntity
|
||||
? $this->map->getQueueableId()->all()
|
||||
: $this->modelKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationships of the entities being queued.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueueableRelations()
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$relations = $this->map->getQueueableRelations()->all();
|
||||
|
||||
if (count($relations) === 0 || $relations === [[]]) {
|
||||
return [];
|
||||
} elseif (count($relations) === 1) {
|
||||
return reset($relations);
|
||||
} else {
|
||||
return array_intersect(...array_values($relations));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection of the entities being queued.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function getQueueableConnection()
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $this->first()->getConnectionName();
|
||||
|
||||
$this->each(function ($model) use ($connection) {
|
||||
if ($model->getConnectionName() !== $connection) {
|
||||
throw new LogicException('Queueing collections with multiple model connections is not supported.');
|
||||
}
|
||||
});
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Eloquent query builder from the collection.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function toQuery()
|
||||
{
|
||||
$model = $this->first();
|
||||
|
||||
if (! $model) {
|
||||
throw new LogicException('Unable to create query for empty collection.');
|
||||
}
|
||||
|
||||
$class = get_class($model);
|
||||
|
||||
if ($this->filter(function ($model) use ($class) {
|
||||
return ! $model instanceof $class;
|
||||
})->isNotEmpty()) {
|
||||
throw new LogicException('Unable to create query for collection with mixed types.');
|
||||
}
|
||||
|
||||
return $model->newModelQuery()->whereKey($this->modelKeys());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait GuardsAttributes
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var string[]|bool
|
||||
*/
|
||||
protected $guarded = ['*'];
|
||||
|
||||
/**
|
||||
* Indicates if all mass assignment is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $unguarded = false;
|
||||
|
||||
/**
|
||||
* The actual columns that exist on the database and can be guarded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $guardableColumns = [];
|
||||
|
||||
/**
|
||||
* Get the fillable attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFillable()
|
||||
{
|
||||
return $this->fillable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fillable attributes for the model.
|
||||
*
|
||||
* @param array $fillable
|
||||
* @return $this
|
||||
*/
|
||||
public function fillable(array $fillable)
|
||||
{
|
||||
$this->fillable = $fillable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new fillable attributes with existing fillable attributes on the model.
|
||||
*
|
||||
* @param array $fillable
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeFillable(array $fillable)
|
||||
{
|
||||
$this->fillable = array_merge($this->fillable, $fillable);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the guarded attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGuarded()
|
||||
{
|
||||
return $this->guarded === false
|
||||
? []
|
||||
: $this->guarded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the guarded attributes for the model.
|
||||
*
|
||||
* @param array $guarded
|
||||
* @return $this
|
||||
*/
|
||||
public function guard(array $guarded)
|
||||
{
|
||||
$this->guarded = $guarded;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new guarded attributes with existing guarded attributes on the model.
|
||||
*
|
||||
* @param array $guarded
|
||||
* @return $this
|
||||
*/
|
||||
public function mergeGuarded(array $guarded)
|
||||
{
|
||||
$this->guarded = array_merge($this->guarded, $guarded);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all mass assignable restrictions.
|
||||
*
|
||||
* @param bool $state
|
||||
* @return void
|
||||
*/
|
||||
public static function unguard($state = true)
|
||||
{
|
||||
static::$unguarded = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the mass assignment restrictions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reguard()
|
||||
{
|
||||
static::$unguarded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current state is "unguarded".
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUnguarded()
|
||||
{
|
||||
return static::$unguarded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given callable while being unguarded.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function unguarded(callable $callback)
|
||||
{
|
||||
if (static::$unguarded) {
|
||||
return $callback();
|
||||
}
|
||||
|
||||
static::unguard();
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::reguard();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given attribute may be mass assigned.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function isFillable($key)
|
||||
{
|
||||
if (static::$unguarded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the key is in the "fillable" array, we can of course assume that it's
|
||||
// a fillable attribute. Otherwise, we will check the guarded array when
|
||||
// we need to determine if the attribute is black-listed on the model.
|
||||
if (in_array($key, $this->getFillable())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the attribute is explicitly listed in the "guarded" array then we can
|
||||
// return false immediately. This means this attribute is definitely not
|
||||
// fillable and there is no point in going any further in this method.
|
||||
if ($this->isGuarded($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return empty($this->getFillable()) &&
|
||||
strpos($key, '.') === false &&
|
||||
! Str::startsWith($key, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key is guarded.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function isGuarded($key)
|
||||
{
|
||||
if (empty($this->getGuarded())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getGuarded() == ['*'] ||
|
||||
! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) ||
|
||||
! $this->isGuardableColumn($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given column is a valid, guardable column.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
protected function isGuardableColumn($key)
|
||||
{
|
||||
if (! isset(static::$guardableColumns[get_class($this)])) {
|
||||
static::$guardableColumns[get_class($this)] = $this->getConnection()
|
||||
->getSchemaBuilder()
|
||||
->getColumnListing($this->getTable());
|
||||
}
|
||||
|
||||
return in_array($key, static::$guardableColumns[get_class($this)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is totally guarded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function totallyGuarded()
|
||||
{
|
||||
return count($this->getFillable()) === 0 && $this->getGuarded() == ['*'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fillable attributes of a given array.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected function fillableFromArray(array $attributes)
|
||||
{
|
||||
if (count($this->getFillable()) > 0 && ! static::$unguarded) {
|
||||
return array_intersect_key($attributes, array_flip($this->getFillable()));
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,1781 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Carbon\CarbonInterface;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\JsonEncodingException;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\LazyLoadingViolationException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
trait HasAttributes
|
||||
{
|
||||
/**
|
||||
* The model's attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* The model attribute's original state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $original = [];
|
||||
|
||||
/**
|
||||
* The changed model attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $changes = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [];
|
||||
|
||||
/**
|
||||
* The attributes that have been cast using custom classes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $classCastCache = [];
|
||||
|
||||
/**
|
||||
* 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',
|
||||
'immutable_date',
|
||||
'immutable_datetime',
|
||||
'immutable_custom_datetime',
|
||||
'int',
|
||||
'integer',
|
||||
'json',
|
||||
'object',
|
||||
'real',
|
||||
'string',
|
||||
'timestamp',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @deprecated Use the "casts" property
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = [];
|
||||
|
||||
/**
|
||||
* The storage format of the model's date columns.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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 encrypter instance that is used to encrypt attributes.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||
*/
|
||||
public static $encrypter;
|
||||
|
||||
/**
|
||||
* 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 (! 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 ($attributes[$key] && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) {
|
||||
$attributes[$key] = $this->serializeDate($attributes[$key]);
|
||||
}
|
||||
|
||||
if ($attributes[$key] && ($this->isCustomDateTimeCast($value) ||
|
||||
$this->isImmutableCustomDateTimeCast($value))) {
|
||||
$attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
|
||||
}
|
||||
|
||||
if ($attributes[$key] && $attributes[$key] instanceof DateTimeInterface &&
|
||||
$this->isClassCastable($key)) {
|
||||
$attributes[$key] = $this->serializeDate($attributes[$key]);
|
||||
}
|
||||
|
||||
if ($attributes[$key] && $this->isClassSerializable($key)) {
|
||||
$attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (! count($this->appends)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->getArrayableItems(
|
||||
array_combine($this->appends, $this->appends)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's relationships in array form.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function relationsToArray()
|
||||
{
|
||||
$attributes = [];
|
||||
|
||||
foreach ($this->getArrayableRelations() as $key => $value) {
|
||||
// If the values implements 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
|
||||
// if it 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 (isset($relation) || is_null($value)) {
|
||||
$attributes[$key] = $relation;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (array_key_exists($key, $this->attributes) ||
|
||||
array_key_exists($key, $this->casts) ||
|
||||
$this->hasGetMutator($key) ||
|
||||
$this->isClassCastable($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;
|
||||
}
|
||||
|
||||
return $this->getRelationValue($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
return $this->getAttributes()[$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->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)
|
||||
{
|
||||
return method_exists($this, $key) ||
|
||||
(static::$relationResolvers[get_class($this)][$key] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a lazy loading violation.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
protected function handleLazyLoadingViolation($key)
|
||||
{
|
||||
if (isset(static::$lazyLoadingViolationCallback)) {
|
||||
return call_user_func(static::$lazyLoadingViolationCallback, $this, $key);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of an attribute using its mutator.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function mutateAttribute($key, $value)
|
||||
{
|
||||
return $this->{'get'.Str::studly($key).'Attribute'}($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)
|
||||
{
|
||||
$value = $this->isClassCastable($key)
|
||||
? $this->getClassCastableAttributeValue($key, $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)
|
||||
{
|
||||
$this->casts = array_merge($this->casts, $casts);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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':
|
||||
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->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)
|
||||
{
|
||||
if (isset($this->classCastCache[$key])) {
|
||||
return $this->classCastCache[$key];
|
||||
} else {
|
||||
$caster = $this->resolveCasterClass($key);
|
||||
|
||||
$value = $caster instanceof CastsInboundAttributes
|
||||
? $value
|
||||
: $caster->get($this, $key, $value, $this->attributes);
|
||||
|
||||
if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
|
||||
unset($this->classCastCache[$key]);
|
||||
} else {
|
||||
$this->classCastCache[$key] = $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of cast for a model attribute.
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function getCastType($key)
|
||||
{
|
||||
if ($this->isCustomDateTimeCast($this->getCasts()[$key])) {
|
||||
return 'custom_datetime';
|
||||
}
|
||||
|
||||
if ($this->isImmutableCustomDateTimeCast($this->getCasts()[$key])) {
|
||||
return 'immutable_custom_datetime';
|
||||
}
|
||||
|
||||
if ($this->isDecimalCast($this->getCasts()[$key])) {
|
||||
return 'decimal';
|
||||
}
|
||||
|
||||
return trim(strtolower($this->getCasts()[$key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the cast type is a custom date time cast.
|
||||
*
|
||||
* @param string $cast
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCustomDateTimeCast($cast)
|
||||
{
|
||||
return strncmp($cast, 'date:', 5) === 0 ||
|
||||
strncmp($cast, 'datetime:', 9) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the cast type is an immutable custom date time cast.
|
||||
*
|
||||
* @param string $cast
|
||||
* @return bool
|
||||
*/
|
||||
protected function isImmutableCustomDateTimeCast($cast)
|
||||
{
|
||||
return strncmp($cast, 'immutable_date:', 15) === 0 ||
|
||||
strncmp($cast, 'immutable_datetime:', 19) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the cast type is a decimal cast.
|
||||
*
|
||||
* @param string $cast
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDecimalCast($cast)
|
||||
{
|
||||
return strncmp($cast, 'decimal:', 8) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// 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 ($value && $this->isDateAttribute($key)) {
|
||||
$value = $this->fromDateTime($value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
$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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an attribute using its mutator.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function setMutatedAttributeValue($key, $value)
|
||||
{
|
||||
return $this->{'set'.Str::studly($key).'Attribute'}($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->attributes[$key] = $this->isEncryptedCastable($key)
|
||||
? $this->castAttributeAsEncryptedString($key, $value)
|
||||
: $value;
|
||||
|
||||
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);
|
||||
|
||||
if (is_null($value)) {
|
||||
$this->attributes = array_merge($this->attributes, array_map(
|
||||
function () {
|
||||
},
|
||||
$this->normalizeCastClassResponse($key, $caster->set(
|
||||
$this, $key, $this->{$key}, $this->attributes
|
||||
))
|
||||
));
|
||||
} else {
|
||||
$this->attributes = array_merge(
|
||||
$this->attributes,
|
||||
$this->normalizeCastClassResponse($key, $caster->set(
|
||||
$this, $key, $value, $this->attributes
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
|
||||
unset($this->classCastCache[$key]);
|
||||
} else {
|
||||
$this->classCastCache[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array attribute with the given key and value set.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
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
|
||||
*/
|
||||
protected function castAttributeAsJson($key, $value)
|
||||
{
|
||||
$value = $this->asJson($value);
|
||||
|
||||
if ($value === false) {
|
||||
throw JsonEncodingException::forAttribute(
|
||||
$this, $key, json_last_error_msg()
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the given value as JSON.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
protected function asJson($value)
|
||||
{
|
||||
return json_encode($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given JSON back into an array or object.
|
||||
*
|
||||
* @param string $value
|
||||
* @param bool $asObject
|
||||
* @return mixed
|
||||
*/
|
||||
public function fromJson($value, $asObject = false)
|
||||
{
|
||||
return json_decode($value, ! $asObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given encrypted string.
|
||||
*
|
||||
* @param string $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function fromEncryptedString($value)
|
||||
{
|
||||
return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the given attribute to an encrypted string.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
protected function castAttributeAsEncryptedString($key, $value)
|
||||
{
|
||||
return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encrypter instance that will be used to encrypt attributes.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||
* @return void
|
||||
*/
|
||||
public static function encryptUsing($encrypter)
|
||||
{
|
||||
static::$encrypter = $encrypter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given float.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function fromFloat($value)
|
||||
{
|
||||
switch ((string) $value) {
|
||||
case 'Infinity':
|
||||
return INF;
|
||||
case '-Infinity':
|
||||
return -INF;
|
||||
case 'NaN':
|
||||
return NAN;
|
||||
default:
|
||||
return (float) $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a decimal as string.
|
||||
*
|
||||
* @param float $value
|
||||
* @param int $decimals
|
||||
* @return string
|
||||
*/
|
||||
protected function asDecimal($value, $decimals)
|
||||
{
|
||||
return number_format($value, $decimals, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// 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 $e) {
|
||||
$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()
|
||||
{
|
||||
if (! $this->usesTimestamps()) {
|
||||
return $this->dates;
|
||||
}
|
||||
|
||||
$defaults = [
|
||||
$this->getCreatedAtColumn(),
|
||||
$this->getUpdatedAtColumn(),
|
||||
];
|
||||
|
||||
return array_unique(array_merge($this->dates, $defaults));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 casts array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCasts()
|
||||
{
|
||||
if ($this->getIncrementing()) {
|
||||
return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
|
||||
}
|
||||
|
||||
return $this->casts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', 'custom_datetime', 'immutable_date', 'immutable_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', '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)
|
||||
{
|
||||
if (! array_key_exists($key, $this->getCasts())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$castType = $this->parseCasterClass($this->getCasts()[$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 key is deviable using a custom class.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\InvalidCastException
|
||||
*/
|
||||
protected function isClassDeviable($key)
|
||||
{
|
||||
return $this->isClassCastable($key) &&
|
||||
method_exists($castType = $this->parseCasterClass($this->getCasts()[$key]), 'increment') &&
|
||||
method_exists($castType, '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->isClassCastable($key) &&
|
||||
method_exists($this->parseCasterClass($this->getCasts()[$key]), 'serialize');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) && strpos($castType, ':') !== false) {
|
||||
$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 strpos($class, ':') === false
|
||||
? $class
|
||||
: explode(':', $class, 2)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the cast class attributes back into the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeAttributesFromClassCasts()
|
||||
{
|
||||
foreach ($this->classCastCache as $key => $value) {
|
||||
$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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->mergeAttributesFromClassCasts();
|
||||
|
||||
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 = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's original attribute values.
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed|array
|
||||
*/
|
||||
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 mixed|array
|
||||
*/
|
||||
protected function getOriginalWithoutRewindingModel($key = null, $default = null)
|
||||
{
|
||||
if ($key) {
|
||||
return $this->transformModelValue(
|
||||
$key, Arr::get($this->original, $key, $default)
|
||||
);
|
||||
}
|
||||
|
||||
return collect($this->original)->mapWithKeys(function ($value, $key) {
|
||||
return [$key => $this->transformModelValue($key, $value)];
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's raw original attribute values.
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return mixed|array
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model or any of the given attribute(s) have been modified.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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 were changed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChanges()
|
||||
{
|
||||
return $this->changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->isDateCastable($key)) {
|
||||
return $this->fromDateTime($attribute) ===
|
||||
$this->fromDateTime($original);
|
||||
} elseif ($this->hasCast($key, ['object', 'collection'])) {
|
||||
return $this->castAttribute($key, $attribute) ==
|
||||
$this->castAttribute($key, $original);
|
||||
} elseif ($this->hasCast($key, ['real', 'float', 'double'])) {
|
||||
if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4;
|
||||
} elseif ($this->hasCast($key, static::$primitiveCastTypes)) {
|
||||
return $this->castAttribute($key, $attribute) ===
|
||||
$this->castAttribute($key, $original);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
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_unique(
|
||||
array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the accessors to append to model arrays.
|
||||
*
|
||||
* @param array $appends
|
||||
* @return $this
|
||||
*/
|
||||
public function setAppends(array $appends)
|
||||
{
|
||||
$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->appends);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mutated attributes for a given instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMutatedAttributes()
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if (! isset(static::$mutatorCache[$class])) {
|
||||
static::cacheMutatedAttributes($class);
|
||||
}
|
||||
|
||||
return static::$mutatorCache[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and cache all the mutated attributes of a class.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function cacheMutatedAttributes($class)
|
||||
{
|
||||
static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->map(function ($match) {
|
||||
return 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];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Events\NullDispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait HasEvents
|
||||
{
|
||||
/**
|
||||
* The event map for the model.
|
||||
*
|
||||
* Allows for object-based events for native Eloquent events.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dispatchesEvents = [];
|
||||
|
||||
/**
|
||||
* User exposed observable events.
|
||||
*
|
||||
* These are extra user-defined events observers may subscribe to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $observables = [];
|
||||
|
||||
/**
|
||||
* Register observers with the model.
|
||||
*
|
||||
* @param object|array|string $classes
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function observe($classes)
|
||||
{
|
||||
$instance = new static;
|
||||
|
||||
foreach (Arr::wrap($classes) as $class) {
|
||||
$instance->registerObserver($class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single observer with the model.
|
||||
*
|
||||
* @param object|string $class
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function registerObserver($class)
|
||||
{
|
||||
$className = $this->resolveObserverClassName($class);
|
||||
|
||||
// When registering a model observer, we will spin through the possible events
|
||||
// and determine if this observer has that method. If it does, we will hook
|
||||
// it into the model's event system, making it convenient to watch these.
|
||||
foreach ($this->getObservableEvents() as $event) {
|
||||
if (method_exists($class, $event)) {
|
||||
static::registerModelEvent($event, $className.'@'.$event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the observer's class name from an object or string.
|
||||
*
|
||||
* @param object|string $class
|
||||
* @return string
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function resolveObserverClassName($class)
|
||||
{
|
||||
if (is_object($class)) {
|
||||
return get_class($class);
|
||||
}
|
||||
|
||||
if (class_exists($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unable to find observer: '.$class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the observable event names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getObservableEvents()
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
'retrieved', 'creating', 'created', 'updating', 'updated',
|
||||
'saving', 'saved', 'restoring', 'restored', 'replicating',
|
||||
'deleting', 'deleted', 'forceDeleted',
|
||||
],
|
||||
$this->observables
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the observable event names.
|
||||
*
|
||||
* @param array $observables
|
||||
* @return $this
|
||||
*/
|
||||
public function setObservableEvents(array $observables)
|
||||
{
|
||||
$this->observables = $observables;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observable event name.
|
||||
*
|
||||
* @param array|mixed $observables
|
||||
* @return void
|
||||
*/
|
||||
public function addObservableEvents($observables)
|
||||
{
|
||||
$this->observables = array_unique(array_merge(
|
||||
$this->observables, is_array($observables) ? $observables : func_get_args()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an observable event name.
|
||||
*
|
||||
* @param array|mixed $observables
|
||||
* @return void
|
||||
*/
|
||||
public function removeObservableEvents($observables)
|
||||
{
|
||||
$this->observables = array_diff(
|
||||
$this->observables, is_array($observables) ? $observables : func_get_args()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a model event with the dispatcher.
|
||||
*
|
||||
* @param string $event
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
protected static function registerModelEvent($event, $callback)
|
||||
{
|
||||
if (isset(static::$dispatcher)) {
|
||||
$name = static::class;
|
||||
|
||||
static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the given event for the model.
|
||||
*
|
||||
* @param string $event
|
||||
* @param bool $halt
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fireModelEvent($event, $halt = true)
|
||||
{
|
||||
if (! isset(static::$dispatcher)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// First, we will get the proper method to call on the event dispatcher, and then we
|
||||
// will attempt to fire a custom, object based event for the given event. If that
|
||||
// returns a result we can return that result, or we'll call the string events.
|
||||
$method = $halt ? 'until' : 'dispatch';
|
||||
|
||||
$result = $this->filterModelEventResults(
|
||||
$this->fireCustomModelEvent($event, $method)
|
||||
);
|
||||
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! empty($result) ? $result : static::$dispatcher->{$method}(
|
||||
"eloquent.{$event}: ".static::class, $this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a custom model event for the given event.
|
||||
*
|
||||
* @param string $event
|
||||
* @param string $method
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function fireCustomModelEvent($event, $method)
|
||||
{
|
||||
if (! isset($this->dispatchesEvents[$event])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));
|
||||
|
||||
if (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the model event results.
|
||||
*
|
||||
* @param mixed $result
|
||||
* @return mixed
|
||||
*/
|
||||
protected function filterModelEventResults($result)
|
||||
{
|
||||
if (is_array($result)) {
|
||||
$result = array_filter($result, function ($response) {
|
||||
return ! is_null($response);
|
||||
});
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a retrieved model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function retrieved($callback)
|
||||
{
|
||||
static::registerModelEvent('retrieved', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a saving model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function saving($callback)
|
||||
{
|
||||
static::registerModelEvent('saving', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a saved model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function saved($callback)
|
||||
{
|
||||
static::registerModelEvent('saved', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an updating model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function updating($callback)
|
||||
{
|
||||
static::registerModelEvent('updating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an updated model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function updated($callback)
|
||||
{
|
||||
static::registerModelEvent('updated', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a creating model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function creating($callback)
|
||||
{
|
||||
static::registerModelEvent('creating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a created model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function created($callback)
|
||||
{
|
||||
static::registerModelEvent('created', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a replicating model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function replicating($callback)
|
||||
{
|
||||
static::registerModelEvent('replicating', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a deleting model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function deleting($callback)
|
||||
{
|
||||
static::registerModelEvent('deleting', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a deleted model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function deleted($callback)
|
||||
{
|
||||
static::registerModelEvent('deleted', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all of the event listeners for the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function flushEventListeners()
|
||||
{
|
||||
if (! isset(static::$dispatcher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = new static;
|
||||
|
||||
foreach ($instance->getObservableEvents() as $event) {
|
||||
static::$dispatcher->forget("eloquent.{$event}: ".static::class);
|
||||
}
|
||||
|
||||
foreach (array_values($instance->dispatchesEvents) as $event) {
|
||||
static::$dispatcher->forget($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event dispatcher instance.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
public static function getEventDispatcher()
|
||||
{
|
||||
return static::$dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event dispatcher instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return void
|
||||
*/
|
||||
public static function setEventDispatcher(Dispatcher $dispatcher)
|
||||
{
|
||||
static::$dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the event dispatcher for models.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unsetEventDispatcher()
|
||||
{
|
||||
static::$dispatcher = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback without firing any model events for any model type.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutEvents(callable $callback)
|
||||
{
|
||||
$dispatcher = static::getEventDispatcher();
|
||||
|
||||
if ($dispatcher) {
|
||||
static::setEventDispatcher(new NullDispatcher($dispatcher));
|
||||
}
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
if ($dispatcher) {
|
||||
static::setEventDispatcher($dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait HasGlobalScopes
|
||||
{
|
||||
/**
|
||||
* Register a new global scope on the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|\Closure|string $scope
|
||||
* @param \Closure|null $implementation
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function addGlobalScope($scope, Closure $implementation = null)
|
||||
{
|
||||
if (is_string($scope) && ! is_null($implementation)) {
|
||||
return static::$globalScopes[static::class][$scope] = $implementation;
|
||||
} elseif ($scope instanceof Closure) {
|
||||
return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope;
|
||||
} elseif ($scope instanceof Scope) {
|
||||
return static::$globalScopes[static::class][get_class($scope)] = $scope;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a model has a global scope.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|string $scope
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasGlobalScope($scope)
|
||||
{
|
||||
return ! is_null(static::getGlobalScope($scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a global scope registered with the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|string $scope
|
||||
* @return \Illuminate\Database\Eloquent\Scope|\Closure|null
|
||||
*/
|
||||
public static function getGlobalScope($scope)
|
||||
{
|
||||
if (is_string($scope)) {
|
||||
return Arr::get(static::$globalScopes, static::class.'.'.$scope);
|
||||
}
|
||||
|
||||
return Arr::get(
|
||||
static::$globalScopes, static::class.'.'.get_class($scope)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global scopes for this class instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGlobalScopes()
|
||||
{
|
||||
return Arr::get(static::$globalScopes, static::class, []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,875 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\ClassMorphViolationException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait HasRelationships
|
||||
{
|
||||
/**
|
||||
* The loaded relationships for the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $relations = [];
|
||||
|
||||
/**
|
||||
* The relationships that should be touched on save.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $touches = [];
|
||||
|
||||
/**
|
||||
* The many to many relationship methods.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $manyMethods = [
|
||||
'belongsToMany', 'morphToMany', 'morphedByMany',
|
||||
];
|
||||
|
||||
/**
|
||||
* The relation resolver callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $relationResolvers = [];
|
||||
|
||||
/**
|
||||
* Define a dynamic relation resolver.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function resolveRelationUsing($name, Closure $callback)
|
||||
{
|
||||
static::$relationResolvers = array_replace_recursive(
|
||||
static::$relationResolvers,
|
||||
[static::class => [$name => $callback]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a one-to-one relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function hasOne($related, $foreignKey = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newHasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasOne relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
|
||||
{
|
||||
return new HasOne($query, $parent, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a has-one-through relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $through
|
||||
* @param string|null $firstKey
|
||||
* @param string|null $secondKey
|
||||
* @param string|null $localKey
|
||||
* @param string|null $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
||||
*/
|
||||
public function hasOneThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
|
||||
{
|
||||
$through = new $through;
|
||||
|
||||
$firstKey = $firstKey ?: $this->getForeignKey();
|
||||
|
||||
$secondKey = $secondKey ?: $through->getForeignKey();
|
||||
|
||||
return $this->newHasOneThrough(
|
||||
$this->newRelatedInstance($related)->newQuery(), $this, $through,
|
||||
$firstKey, $secondKey, $localKey ?: $this->getKeyName(),
|
||||
$secondLocalKey ?: $through->getKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasOneThrough relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $farParent
|
||||
* @param \Illuminate\Database\Eloquent\Model $throughParent
|
||||
* @param string $firstKey
|
||||
* @param string $secondKey
|
||||
* @param string $localKey
|
||||
* @param string $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
||||
*/
|
||||
protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
|
||||
{
|
||||
return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic one-to-one relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $name
|
||||
* @param string|null $type
|
||||
* @param string|null $id
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
|
||||
*/
|
||||
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
[$type, $id] = $this->getMorphs($name, $type, $id);
|
||||
|
||||
$table = $instance->getTable();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphOne relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
|
||||
*/
|
||||
protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey)
|
||||
{
|
||||
return new MorphOne($query, $parent, $type, $id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an inverse one-to-one or many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $ownerKey
|
||||
* @param string|null $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
|
||||
{
|
||||
// If no relation name was given, we will use this debug backtrace to extract
|
||||
// the calling method's name and use that as the relationship name as most
|
||||
// of the time this will be what we desire to use for the relationships.
|
||||
if (is_null($relation)) {
|
||||
$relation = $this->guessBelongsToRelation();
|
||||
}
|
||||
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
// If no foreign key was supplied, we can use a backtrace to guess the proper
|
||||
// foreign key name by using the name of the relationship function, which
|
||||
// when combined with an "_id" should conventionally match the columns.
|
||||
if (is_null($foreignKey)) {
|
||||
$foreignKey = Str::snake($relation).'_'.$instance->getKeyName();
|
||||
}
|
||||
|
||||
// Once we have the foreign key names, we'll just create a new Eloquent query
|
||||
// for the related models and returns the relationship instance which will
|
||||
// actually be responsible for retrieving and hydrating every relations.
|
||||
$ownerKey = $ownerKey ?: $instance->getKeyName();
|
||||
|
||||
return $this->newBelongsTo(
|
||||
$instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new BelongsTo relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $child
|
||||
* @param string $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
|
||||
{
|
||||
return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse one-to-one or many relationship.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param string|null $type
|
||||
* @param string|null $id
|
||||
* @param string|null $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function morphTo($name = null, $type = null, $id = null, $ownerKey = null)
|
||||
{
|
||||
// If no name is provided, we will use the backtrace to get the function name
|
||||
// since that is most likely the name of the polymorphic interface. We can
|
||||
// use that to get both the class and foreign key that will be utilized.
|
||||
$name = $name ?: $this->guessBelongsToRelation();
|
||||
|
||||
[$type, $id] = $this->getMorphs(
|
||||
Str::snake($name), $type, $id
|
||||
);
|
||||
|
||||
// If the type value is null it is probably safe to assume we're eager loading
|
||||
// the relationship. In this case we'll just pass in a dummy query where we
|
||||
// need to remove any eager loads that may already be defined on a model.
|
||||
return is_null($class = $this->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 $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
protected function morphEagerTo($name, $type, $id, $ownerKey)
|
||||
{
|
||||
return $this->newMorphTo(
|
||||
$this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse one-to-one or many relationship.
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $ownerKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
protected function morphInstanceTo($target, $name, $type, $id, $ownerKey)
|
||||
{
|
||||
$instance = $this->newRelatedInstance(
|
||||
static::getActualClassNameForMorph($target)
|
||||
);
|
||||
|
||||
return $this->newMorphTo(
|
||||
$instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphTo relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $type
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
|
||||
{
|
||||
return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the actual class name for a given morph class.
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
public static function getActualClassNameForMorph($class)
|
||||
{
|
||||
return Arr::get(Relation::morphMap() ?: [], $class, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the "belongs to" relationship name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function guessBelongsToRelation()
|
||||
{
|
||||
[$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
|
||||
return $caller['function'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a one-to-many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string|null $foreignKey
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function hasMany($related, $foreignKey = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newHasMany(
|
||||
$instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasMany relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
|
||||
{
|
||||
return new HasMany($query, $parent, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a has-many-through relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $through
|
||||
* @param string|null $firstKey
|
||||
* @param string|null $secondKey
|
||||
* @param string|null $localKey
|
||||
* @param string|null $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
|
||||
*/
|
||||
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
|
||||
{
|
||||
$through = new $through;
|
||||
|
||||
$firstKey = $firstKey ?: $this->getForeignKey();
|
||||
|
||||
$secondKey = $secondKey ?: $through->getForeignKey();
|
||||
|
||||
return $this->newHasManyThrough(
|
||||
$this->newRelatedInstance($related)->newQuery(),
|
||||
$this,
|
||||
$through,
|
||||
$firstKey,
|
||||
$secondKey,
|
||||
$localKey ?: $this->getKeyName(),
|
||||
$secondLocalKey ?: $through->getKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new HasManyThrough relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $farParent
|
||||
* @param \Illuminate\Database\Eloquent\Model $throughParent
|
||||
* @param string $firstKey
|
||||
* @param string $secondKey
|
||||
* @param string $localKey
|
||||
* @param string $secondLocalKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
|
||||
*/
|
||||
protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
|
||||
{
|
||||
return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic one-to-many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $name
|
||||
* @param string|null $type
|
||||
* @param string|null $id
|
||||
* @param string|null $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
*/
|
||||
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
|
||||
{
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
// Here we will gather up the morph type and ID for the relationship so that we
|
||||
// can properly query the intermediate table of a relation. Finally, we will
|
||||
// get the table and create the relationship instances for the developers.
|
||||
[$type, $id] = $this->getMorphs($name, $type, $id);
|
||||
|
||||
$table = $instance->getTable();
|
||||
|
||||
$localKey = $localKey ?: $this->getKeyName();
|
||||
|
||||
return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphMany relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
*/
|
||||
protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
|
||||
{
|
||||
return new MorphMany($query, $parent, $type, $id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a many-to-many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string|null $table
|
||||
* @param string|null $foreignPivotKey
|
||||
* @param string|null $relatedPivotKey
|
||||
* @param string|null $parentKey
|
||||
* @param string|null $relatedKey
|
||||
* @param string|null $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
|
||||
$parentKey = null, $relatedKey = null, $relation = null)
|
||||
{
|
||||
// If no relationship name was passed, we will pull backtraces to get the
|
||||
// name of the calling function. We will use that function name as the
|
||||
// title of this relation since that is a great convention to apply.
|
||||
if (is_null($relation)) {
|
||||
$relation = $this->guessBelongsToManyRelation();
|
||||
}
|
||||
|
||||
// First, we'll need to determine the foreign key and "other key" for the
|
||||
// relationship. Once we have determined the keys we'll make the query
|
||||
// instances as well as the relationship instances we need for this.
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
|
||||
|
||||
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
|
||||
|
||||
// If no table name was provided, we can guess it by concatenating the two
|
||||
// models using underscores in alphabetical order. The two model names
|
||||
// are transformed to snake case from their default CamelCase also.
|
||||
if (is_null($table)) {
|
||||
$table = $this->joiningTable($related, $instance);
|
||||
}
|
||||
|
||||
return $this->newBelongsToMany(
|
||||
$instance->newQuery(), $this, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey ?: $this->getKeyName(),
|
||||
$relatedKey ?: $instance->getKeyName(), $relation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new BelongsToMany relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $table
|
||||
* @param string $foreignPivotKey
|
||||
* @param string $relatedPivotKey
|
||||
* @param string $parentKey
|
||||
* @param string $relatedKey
|
||||
* @param string|null $relationName
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey,
|
||||
$parentKey, $relatedKey, $relationName = null)
|
||||
{
|
||||
return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic many-to-many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $name
|
||||
* @param string|null $table
|
||||
* @param string|null $foreignPivotKey
|
||||
* @param string|null $relatedPivotKey
|
||||
* @param string|null $parentKey
|
||||
* @param string|null $relatedKey
|
||||
* @param bool $inverse
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
|
||||
*/
|
||||
public function morphToMany($related, $name, $table = null, $foreignPivotKey = null,
|
||||
$relatedPivotKey = null, $parentKey = null,
|
||||
$relatedKey = null, $inverse = false)
|
||||
{
|
||||
$caller = $this->guessBelongsToManyRelation();
|
||||
|
||||
// First, we will need to determine the foreign key and "other key" for the
|
||||
// relationship. Once we have determined the keys we will make the query
|
||||
// instances, as well as the relationship instances we need for these.
|
||||
$instance = $this->newRelatedInstance($related);
|
||||
|
||||
$foreignPivotKey = $foreignPivotKey ?: $name.'_id';
|
||||
|
||||
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
|
||||
|
||||
// Now we're ready to create a new query builder for this related model and
|
||||
// the relationship instances for this relation. This relations will set
|
||||
// appropriate query constraints then entirely manages the hydrations.
|
||||
if (! $table) {
|
||||
$words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
$lastWord = array_pop($words);
|
||||
|
||||
$table = implode('', $words).Str::plural($lastWord);
|
||||
}
|
||||
|
||||
return $this->newMorphToMany(
|
||||
$instance->newQuery(), $this, $name, $table,
|
||||
$foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(),
|
||||
$relatedKey ?: $instance->getKeyName(), $caller, $inverse
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new MorphToMany relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $name
|
||||
* @param string $table
|
||||
* @param string $foreignPivotKey
|
||||
* @param string $relatedPivotKey
|
||||
* @param string $parentKey
|
||||
* @param string $relatedKey
|
||||
* @param string|null $relationName
|
||||
* @param bool $inverse
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
|
||||
*/
|
||||
protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey,
|
||||
$relationName = null, $inverse = false)
|
||||
{
|
||||
return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey,
|
||||
$relationName, $inverse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a polymorphic, inverse many-to-many relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @param string $name
|
||||
* @param string|null $table
|
||||
* @param string|null $foreignPivotKey
|
||||
* @param string|null $relatedPivotKey
|
||||
* @param string|null $parentKey
|
||||
* @param string|null $relatedKey
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
|
||||
*/
|
||||
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
|
||||
$relatedPivotKey = null, $parentKey = null, $relatedKey = null)
|
||||
{
|
||||
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
|
||||
|
||||
// For the inverse of the polymorphic many-to-many relations, we will change
|
||||
// the way we determine the foreign and other keys, as it is the opposite
|
||||
// of the morph-to-many method since we're figuring out these inverses.
|
||||
$relatedPivotKey = $relatedPivotKey ?: $name.'_id';
|
||||
|
||||
return $this->morphToMany(
|
||||
$related, $name, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey, true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationship name of the belongsToMany relationship.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function guessBelongsToManyRelation()
|
||||
{
|
||||
$caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
|
||||
return ! in_array(
|
||||
$trace['function'],
|
||||
array_merge(static::$manyMethods, ['guessBelongsToManyRelation'])
|
||||
);
|
||||
});
|
||||
|
||||
return ! is_null($caller) ? $caller['function'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the joining table name for a many-to-many relation.
|
||||
*
|
||||
* @param string $related
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $instance
|
||||
* @return string
|
||||
*/
|
||||
public function joiningTable($related, $instance = null)
|
||||
{
|
||||
// The joining table name, by convention, is simply the snake cased models
|
||||
// sorted alphabetically and concatenated with an underscore, so we can
|
||||
// just sort the models and join them together to get the table name.
|
||||
$segments = [
|
||||
$instance ? $instance->joiningTableSegment()
|
||||
: Str::snake(class_basename($related)),
|
||||
$this->joiningTableSegment(),
|
||||
];
|
||||
|
||||
// Now that we have the model names in an array we can just sort them and
|
||||
// use the implode function to join them together with an underscores,
|
||||
// which is typically used by convention within the database system.
|
||||
sort($segments);
|
||||
|
||||
return strtolower(implode('_', $segments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this model's half of the intermediate table name for belongsToMany relationships.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function joiningTableSegment()
|
||||
{
|
||||
return Str::snake(class_basename($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model touches a given relation.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return bool
|
||||
*/
|
||||
public function touches($relation)
|
||||
{
|
||||
return in_array($relation, $this->getTouchedRelations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch the owning relations of the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function touchOwners()
|
||||
{
|
||||
foreach ($this->getTouchedRelations() as $relation) {
|
||||
$this->$relation()->touch();
|
||||
|
||||
if ($this->$relation instanceof self) {
|
||||
$this->$relation->fireModelEvent('saved', false);
|
||||
|
||||
$this->$relation->touchOwners();
|
||||
} elseif ($this->$relation instanceof Collection) {
|
||||
$this->$relation->each->touchOwners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the polymorphic relationship columns.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @return array
|
||||
*/
|
||||
protected function getMorphs($name, $type, $id)
|
||||
{
|
||||
return [$type ?: $name.'_type', $id ?: $name.'_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for polymorphic relations.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array(static::class, $morphMap)) {
|
||||
return array_search(static::class, $morphMap, true);
|
||||
}
|
||||
|
||||
if (Relation::requiresMorphMap()) {
|
||||
throw new ClassMorphViolationException($this);
|
||||
}
|
||||
|
||||
return static::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance for a related model.
|
||||
*
|
||||
* @param string $class
|
||||
* @return mixed
|
||||
*/
|
||||
protected function newRelatedInstance($class)
|
||||
{
|
||||
return tap(new $class, function ($instance) {
|
||||
if (! $instance->getConnectionName()) {
|
||||
$instance->setConnection($this->connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the loaded relations for the instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRelations()
|
||||
{
|
||||
return $this->relations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified relationship.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRelation($relation)
|
||||
{
|
||||
return $this->relations[$relation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given relation is loaded.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function relationLoaded($key)
|
||||
{
|
||||
return array_key_exists($key, $this->relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given relationship on the model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setRelation($relation, $value)
|
||||
{
|
||||
$this->relations[$relation] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a loaded relationship.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetRelation($relation)
|
||||
{
|
||||
unset($this->relations[$relation]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entire relations array on the model.
|
||||
*
|
||||
* @param array $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function setRelations(array $relations)
|
||||
{
|
||||
$this->relations = $relations;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the instance and unset all the loaded relations.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutRelations()
|
||||
{
|
||||
$model = clone $this;
|
||||
|
||||
return $model->unsetRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset all the loaded relations for the instance.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetRelations()
|
||||
{
|
||||
$this->relations = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationships that are touched on save.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTouchedRelations()
|
||||
{
|
||||
return $this->touches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relationships that are touched on save.
|
||||
*
|
||||
* @param array $touches
|
||||
* @return $this
|
||||
*/
|
||||
public function setTouchedRelations(array $touches)
|
||||
{
|
||||
$this->touches = $touches;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
trait HasTimestamps
|
||||
{
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* Update the model's update timestamp.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function touch()
|
||||
{
|
||||
if (! $this->usesTimestamps()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->updateTimestamps();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the creation and update timestamps.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateTimestamps()
|
||||
{
|
||||
$time = $this->freshTimestamp();
|
||||
|
||||
$updatedAtColumn = $this->getUpdatedAtColumn();
|
||||
|
||||
if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) {
|
||||
$this->setUpdatedAt($time);
|
||||
}
|
||||
|
||||
$createdAtColumn = $this->getCreatedAtColumn();
|
||||
|
||||
if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) {
|
||||
$this->setCreatedAt($time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the "created at" attribute.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setCreatedAt($value)
|
||||
{
|
||||
$this->{$this->getCreatedAtColumn()} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the "updated at" attribute.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setUpdatedAt($value)
|
||||
{
|
||||
$this->{$this->getUpdatedAtColumn()} = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh timestamp for the model.
|
||||
*
|
||||
* @return \Illuminate\Support\Carbon
|
||||
*/
|
||||
public function freshTimestamp()
|
||||
{
|
||||
return Date::now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fresh timestamp for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function freshTimestampString()
|
||||
{
|
||||
return $this->fromDateTime($this->freshTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model uses timestamps.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usesTimestamps()
|
||||
{
|
||||
return $this->timestamps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "created at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCreatedAtColumn()
|
||||
{
|
||||
return static::CREATED_AT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "updated at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUpdatedAtColumn()
|
||||
{
|
||||
return static::UPDATED_AT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "created at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedCreatedAtColumn()
|
||||
{
|
||||
return $this->qualifyColumn($this->getCreatedAtColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "updated at" column.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQualifiedUpdatedAtColumn()
|
||||
{
|
||||
return $this->qualifyColumn($this->getUpdatedAtColumn());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
|
||||
trait HidesAttributes
|
||||
{
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be visible in serialization.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $visible = [];
|
||||
|
||||
/**
|
||||
* Get the hidden attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHidden()
|
||||
{
|
||||
return $this->hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hidden attributes for the model.
|
||||
*
|
||||
* @param array $hidden
|
||||
* @return $this
|
||||
*/
|
||||
public function setHidden(array $hidden)
|
||||
{
|
||||
$this->hidden = $hidden;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visible attributes for the model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVisible()
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visible attributes for the model.
|
||||
*
|
||||
* @param array $visible
|
||||
* @return $this
|
||||
*/
|
||||
public function setVisible(array $visible)
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible.
|
||||
*
|
||||
* @param array|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisible($attributes)
|
||||
{
|
||||
$attributes = is_array($attributes) ? $attributes : func_get_args();
|
||||
|
||||
$this->hidden = array_diff($this->hidden, $attributes);
|
||||
|
||||
if (! empty($this->visible)) {
|
||||
$this->visible = array_merge($this->visible, $attributes);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically hidden, attributes visible if the given truth test passes.
|
||||
*
|
||||
* @param bool|Closure $condition
|
||||
* @param array|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeVisibleIf($condition, $attributes)
|
||||
{
|
||||
return value($condition, $this) ? $this->makeVisible($attributes) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden.
|
||||
*
|
||||
* @param array|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHidden($attributes)
|
||||
{
|
||||
$this->hidden = array_merge(
|
||||
$this->hidden, is_array($attributes) ? $attributes : func_get_args()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given, typically visible, attributes hidden if the given truth test passes.
|
||||
*
|
||||
* @param bool|Closure $condition
|
||||
* @param array|string|null $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function makeHiddenIf($condition, $attributes)
|
||||
{
|
||||
return value($condition, $this) ? $this->makeHidden($attributes) : $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,705 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait QueriesRelationships
|
||||
{
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation|string $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
if (strpos($relation, '.') !== false) {
|
||||
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
|
||||
}
|
||||
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if ($relation instanceof MorphTo) {
|
||||
return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback);
|
||||
}
|
||||
|
||||
// If we only need to check for the existence of the relation, then we can optimize
|
||||
// the subquery to only run a "where exists" clause instead of this full "count"
|
||||
// clause. This will make these queries run much faster compared with a count.
|
||||
$method = $this->canUseExistsForExistenceCheck($operator, $count)
|
||||
? 'getRelationExistenceQuery'
|
||||
: 'getRelationExistenceCountQuery';
|
||||
|
||||
$hasQuery = $relation->{$method}(
|
||||
$relation->getRelated()->newQueryWithoutRelationships(), $this
|
||||
);
|
||||
|
||||
// Next we will call any given callback as an "anonymous" scope so they can get the
|
||||
// proper logical grouping of the where clauses if needed by this Eloquent query
|
||||
// builder. Then, we will be ready to finalize and return this query instance.
|
||||
if ($callback) {
|
||||
$hasQuery->callScope($callback);
|
||||
}
|
||||
|
||||
return $this->addHasWhere(
|
||||
$hasQuery, $relation, $operator, $count, $boolean
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add nested relationship count / exists conditions to the query.
|
||||
*
|
||||
* Sets up recursive call to whereHas until we finish the nested relation.
|
||||
*
|
||||
* @param string $relations
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
|
||||
{
|
||||
$relations = explode('.', $relations);
|
||||
|
||||
$doesntHave = $operator === '<' && $count === 1;
|
||||
|
||||
if ($doesntHave) {
|
||||
$operator = '>=';
|
||||
$count = 1;
|
||||
}
|
||||
|
||||
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
|
||||
// In order to nest "has", we need to add count relation constraints on the
|
||||
// callback Closure. We'll do this by simply passing the Closure its own
|
||||
// reference to itself so it calls itself recursively on each segment.
|
||||
count($relations) > 1
|
||||
? $q->whereHas(array_shift($relations), $closure)
|
||||
: $q->has(array_shift($relations), $operator, $count, 'and', $callback);
|
||||
};
|
||||
|
||||
return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param string $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orHas($relation, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->has($relation, $operator, $count, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function doesntHave($relation, $boolean = 'and', Closure $callback = null)
|
||||
{
|
||||
return $this->has($relation, '<', 1, $boolean, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orDoesntHave($relation)
|
||||
{
|
||||
return $this->doesntHave($relation, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->has($relation, $operator, $count, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->has($relation, $operator, $count, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereDoesntHave($relation, Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHave($relation, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereDoesntHave($relation, Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHave($relation, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
$types = (array) $types;
|
||||
|
||||
if ($types === ['*']) {
|
||||
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();
|
||||
}
|
||||
|
||||
foreach ($types as &$type) {
|
||||
$type = Relation::getMorphedModel($type) ?? $type;
|
||||
}
|
||||
|
||||
return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) {
|
||||
foreach ($types as $type) {
|
||||
$query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) {
|
||||
$belongsTo = $this->getBelongsToRelation($relation, $type);
|
||||
|
||||
if ($callback) {
|
||||
$callback = function ($query) use ($callback, $type) {
|
||||
return $callback($query, $type);
|
||||
};
|
||||
}
|
||||
|
||||
$query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass())
|
||||
->whereHas($belongsTo, $callback, $operator, $count);
|
||||
});
|
||||
}
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BelongsTo relationship for a single polymorphic type.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation
|
||||
* @param string $type
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
protected function getBelongsToRelation(MorphTo $relation, $type)
|
||||
{
|
||||
$belongsTo = Relation::noConstraints(function () use ($relation, $type) {
|
||||
return $this->model->belongsTo(
|
||||
$type,
|
||||
$relation->getForeignKeyName(),
|
||||
$relation->getOwnerKeyName()
|
||||
);
|
||||
});
|
||||
|
||||
$belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());
|
||||
|
||||
return $belongsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orHasMorph($relation, $types, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, $operator, $count, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param string $boolean
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function doesntHaveMorph($relation, $types, $boolean = 'and', Closure $callback = null)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orDoesntHaveMorph($relation, $types)
|
||||
{
|
||||
return $this->doesntHaveMorph($relation, $types, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
|
||||
{
|
||||
return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereDoesntHaveMorph($relation, $types, Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHaveMorph($relation, $types, 'and', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|null $callback
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null)
|
||||
{
|
||||
return $this->doesntHaveMorph($relation, $types, 'or', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic where clause to a relationship query.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereHas($relation, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "or where" clause to a relationship query.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereRelation($relation, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with a where clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a polymorphic relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param string|array $types
|
||||
* @param \Closure|string|array|\Illuminate\Database\Query\Expression $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
|
||||
$query->where($column, $operator, $value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a morph-to relationship condition to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function whereMorphedTo($relation, $model, $boolean = 'and')
|
||||
{
|
||||
if (is_string($relation)) {
|
||||
$relation = $this->getRelationWithoutConstraints($relation);
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$morphMap = Relation::morphMap();
|
||||
|
||||
if (! empty($morphMap) && in_array($model, $morphMap)) {
|
||||
$model = array_search($model, $morphMap, true);
|
||||
}
|
||||
|
||||
return $this->where($relation->getMorphType(), $model, null, $boolean);
|
||||
}
|
||||
|
||||
return $this->where(function ($query) use ($relation, $model) {
|
||||
$query->where($relation->getMorphType(), $model->getMorphClass())
|
||||
->where($relation->getForeignKeyName(), $model->getKey());
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a morph-to relationship condition to the query with an "or where" clause.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function orWhereMorphedTo($relation, $model)
|
||||
{
|
||||
return $this->whereMorphedTo($relation, $model, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include an aggregate value for a relationship.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @param string $column
|
||||
* @param string $function
|
||||
* @return $this
|
||||
*/
|
||||
public function withAggregate($relations, $column, $function = null)
|
||||
{
|
||||
if (empty($relations)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_null($this->query->columns)) {
|
||||
$this->query->select([$this->query->from.'.*']);
|
||||
}
|
||||
|
||||
$relations = is_array($relations) ? $relations : [$relations];
|
||||
|
||||
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
|
||||
// First we will determine if the name has been aliased using an "as" clause on the name
|
||||
// and if it has we will extract the actual relationship name and the desired name of
|
||||
// the resulting column. This allows multiple aggregates on the same relationships.
|
||||
$segments = explode(' ', $name);
|
||||
|
||||
unset($alias);
|
||||
|
||||
if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
|
||||
[$name, $alias] = [$segments[0], $segments[2]];
|
||||
}
|
||||
|
||||
$relation = $this->getRelationWithoutConstraints($name);
|
||||
|
||||
if ($function) {
|
||||
$hashedColumn = $this->getQuery()->from === $relation->getQuery()->getQuery()->from
|
||||
? "{$relation->getRelationCountHash(false)}.$column"
|
||||
: $column;
|
||||
|
||||
$wrappedColumn = $this->getQuery()->getGrammar()->wrap(
|
||||
$column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
|
||||
);
|
||||
|
||||
$expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn);
|
||||
} else {
|
||||
$expression = $column;
|
||||
}
|
||||
|
||||
// Here, we will grab the relationship sub-query and prepare to add it to the main query
|
||||
// as a sub-select. First, we'll get the "has" query and use that to get the relation
|
||||
// sub-query. We'll format this relationship name and append this column if needed.
|
||||
$query = $relation->getRelationExistenceQuery(
|
||||
$relation->getRelated()->newQuery(), $this, new Expression($expression)
|
||||
)->setBindings([], 'select');
|
||||
|
||||
$query->callScope($constraints);
|
||||
|
||||
$query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
|
||||
|
||||
// If the query contains certain elements like orderings / more than one column selected
|
||||
// then we will remove those elements from the query so that it will execute properly
|
||||
// when given to the database. Otherwise, we may receive SQL errors or poor syntax.
|
||||
$query->orders = null;
|
||||
$query->setBindings([], 'order');
|
||||
|
||||
if (count($query->columns) > 1) {
|
||||
$query->columns = [$query->columns[0]];
|
||||
$query->bindings['select'] = [];
|
||||
}
|
||||
|
||||
// Finally, we will make the proper column alias to the query and run this sub-select on
|
||||
// the query builder. Then, we will return the builder instance back to the developer
|
||||
// for further constraint chaining that needs to take place on the query as needed.
|
||||
$alias = $alias ?? Str::snake(
|
||||
preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column")
|
||||
);
|
||||
|
||||
if ($function === 'exists') {
|
||||
$this->selectRaw(
|
||||
sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)),
|
||||
$query->getBindings()
|
||||
)->withCasts([$alias => 'bool']);
|
||||
} else {
|
||||
$this->selectSub(
|
||||
$function ? $query : $query->limit(1),
|
||||
$alias
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to count the relations.
|
||||
*
|
||||
* @param mixed $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function withCount($relations)
|
||||
{
|
||||
return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the max of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withMax($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'max');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the min of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withMin($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the sum of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withSum($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'sum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the average of the relation's column.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function withAvg($relation, $column)
|
||||
{
|
||||
return $this->withAggregate($relation, $column, 'avg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subselect queries to include the existence of related models.
|
||||
*
|
||||
* @param string|array $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function withExists($relation)
|
||||
{
|
||||
return $this->withAggregate($relation, '*', 'exists');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "has" condition where clause to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $hasQuery
|
||||
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean)
|
||||
{
|
||||
$hasQuery->mergeConstraintsFrom($relation->getQuery());
|
||||
|
||||
return $this->canUseExistsForExistenceCheck($operator, $count)
|
||||
? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1)
|
||||
: $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the where constraints from another query to the current query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $from
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function mergeConstraintsFrom(Builder $from)
|
||||
{
|
||||
$whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
|
||||
|
||||
// Here we have some other query that we want to merge the where constraints from. We will
|
||||
// copy over any where constraints on the query as well as remove any global scopes the
|
||||
// query might have removed. Then we will return ourselves with the finished merging.
|
||||
return $this->withoutGlobalScopes(
|
||||
$from->removedScopes()
|
||||
)->mergeWheres(
|
||||
$from->getQuery()->wheres, $whereBindings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sub-query count clause to this query.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
|
||||
{
|
||||
$this->query->addBinding($query->getBindings(), 'where');
|
||||
|
||||
return $this->where(
|
||||
new Expression('('.$query->toSql().')'),
|
||||
$operator,
|
||||
is_numeric($count) ? new Expression($count) : $count,
|
||||
$boolean
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "has relation" base query instance.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
protected function getRelationWithoutConstraints($relation)
|
||||
{
|
||||
return Relation::noConstraints(function () use ($relation) {
|
||||
return $this->getModel()->{$relation}();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can run an "exists" query to optimize performance.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param int $count
|
||||
* @return bool
|
||||
*/
|
||||
protected function canUseExistsForExistenceCheck($operator, $count)
|
||||
{
|
||||
return ($operator === '>=' || $operator === '<') && $count === 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BelongsToManyRelationship
|
||||
{
|
||||
/**
|
||||
* The related factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The pivot attributes / attribute resolver.
|
||||
*
|
||||
* @var callable|array
|
||||
*/
|
||||
protected $pivot;
|
||||
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* Create a new attached relationship definition.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model $factory
|
||||
* @param callable|array $pivot
|
||||
* @param string $relationship
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($factory, $pivot, $relationship)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->pivot = $pivot;
|
||||
$this->relationship = $relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the attached relationship for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
public function createFor(Model $model)
|
||||
{
|
||||
Collection::wrap($this->factory instanceof Factory ? $this->factory->create([], $model) : $this->factory)->each(function ($attachable) use ($model) {
|
||||
$model->{$this->relationship}()->attach(
|
||||
$attachable,
|
||||
is_callable($this->pivot) ? call_user_func($this->pivot, $model) : $this->pivot
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class BelongsToRelationship
|
||||
{
|
||||
/**
|
||||
* The related factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* The cached, resolved parent instance ID.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $resolved;
|
||||
|
||||
/**
|
||||
* Create a new "belongs to" relationship definition.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory
|
||||
* @param string $relationship
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($factory, $relationship)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->relationship = $relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent model attributes and resolvers for the given child model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return array
|
||||
*/
|
||||
public function attributesFor(Model $model)
|
||||
{
|
||||
$relationship = $model->{$this->relationship}();
|
||||
|
||||
return $relationship instanceof MorphTo ? [
|
||||
$relationship->getMorphType() => $this->factory instanceof Factory ? $this->factory->newModel()->getMorphClass() : $this->factory->getMorphClass(),
|
||||
$relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()),
|
||||
] : [
|
||||
$relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the deferred resolver for this relationship's parent ID.
|
||||
*
|
||||
* @param string|null $key
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function resolver($key)
|
||||
{
|
||||
return function () use ($key) {
|
||||
if (! $this->resolved) {
|
||||
$instance = $this->factory instanceof Factory ? $this->factory->create() : $this->factory;
|
||||
|
||||
return $this->resolved = $key ? $instance->{$key} : $instance->getKey();
|
||||
}
|
||||
|
||||
return $this->resolved;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,820 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Closure;
|
||||
use Faker\Generator;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use Throwable;
|
||||
|
||||
abstract class Factory
|
||||
{
|
||||
use ForwardsCalls, Macroable {
|
||||
__call as macroCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* The number of models that should be generated.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* The state transformations that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $states;
|
||||
|
||||
/**
|
||||
* The parent relationships that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $has;
|
||||
|
||||
/**
|
||||
* The child relationships that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $for;
|
||||
|
||||
/**
|
||||
* The "after making" callbacks that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $afterMaking;
|
||||
|
||||
/**
|
||||
* The "after creating" callbacks that will be applied to the model.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
protected $afterCreating;
|
||||
|
||||
/**
|
||||
* The name of the database connection that will be used to create the models.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The current Faker instance.
|
||||
*
|
||||
* @var \Faker\Generator
|
||||
*/
|
||||
protected $faker;
|
||||
|
||||
/**
|
||||
* The default namespace where factories reside.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $namespace = 'Database\\Factories\\';
|
||||
|
||||
/**
|
||||
* The default model name resolver.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected static $modelNameResolver;
|
||||
|
||||
/**
|
||||
* The factory name resolver.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected static $factoryNameResolver;
|
||||
|
||||
/**
|
||||
* Create a new factory instance.
|
||||
*
|
||||
* @param int|null $count
|
||||
* @param \Illuminate\Support\Collection|null $states
|
||||
* @param \Illuminate\Support\Collection|null $has
|
||||
* @param \Illuminate\Support\Collection|null $for
|
||||
* @param \Illuminate\Support\Collection|null $afterMaking
|
||||
* @param \Illuminate\Support\Collection|null $afterCreating
|
||||
* @param string|null $connection
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($count = null,
|
||||
?Collection $states = null,
|
||||
?Collection $has = null,
|
||||
?Collection $for = null,
|
||||
?Collection $afterMaking = null,
|
||||
?Collection $afterCreating = null,
|
||||
$connection = null)
|
||||
{
|
||||
$this->count = $count;
|
||||
$this->states = $states ?: new Collection;
|
||||
$this->has = $has ?: new Collection;
|
||||
$this->for = $for ?: new Collection;
|
||||
$this->afterMaking = $afterMaking ?: new Collection;
|
||||
$this->afterCreating = $afterCreating ?: new Collection;
|
||||
$this->connection = $connection;
|
||||
$this->faker = $this->withFaker();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function definition();
|
||||
|
||||
/**
|
||||
* Get a new factory instance for the given attributes.
|
||||
*
|
||||
* @param callable|array $attributes
|
||||
* @return static
|
||||
*/
|
||||
public static function new($attributes = [])
|
||||
{
|
||||
return (new static)->state($attributes)->configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new factory instance for the given number of models.
|
||||
*
|
||||
* @param int $count
|
||||
* @return static
|
||||
*/
|
||||
public static function times(int $count)
|
||||
{
|
||||
return static::new()->count($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the factory.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function configure()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw attributes generated by the factory.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return array
|
||||
*/
|
||||
public function raw($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
if ($this->count === null) {
|
||||
return $this->state($attributes)->getExpandedAttributes($parent);
|
||||
}
|
||||
|
||||
return array_map(function () use ($attributes, $parent) {
|
||||
return $this->state($attributes)->getExpandedAttributes($parent);
|
||||
}, range(1, $this->count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single model and persist it to the database.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function createOne($attributes = [])
|
||||
{
|
||||
return $this->count(null)->create($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single model and persist it to the database.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function createOneQuietly($attributes = [])
|
||||
{
|
||||
return $this->count(null)->createQuietly($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database.
|
||||
*
|
||||
* @param iterable $records
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function createMany(iterable $records)
|
||||
{
|
||||
return new EloquentCollection(
|
||||
collect($records)->map(function ($record) {
|
||||
return $this->state($record)->create();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database.
|
||||
*
|
||||
* @param iterable $records
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function createManyQuietly(iterable $records)
|
||||
{
|
||||
return Model::withoutEvents(function () use ($records) {
|
||||
return $this->createMany($records);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function create($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
if (! empty($attributes)) {
|
||||
return $this->state($attributes)->create([], $parent);
|
||||
}
|
||||
|
||||
$results = $this->make($attributes, $parent);
|
||||
|
||||
if ($results instanceof Model) {
|
||||
$this->store(collect([$results]));
|
||||
|
||||
$this->callAfterCreating(collect([$results]), $parent);
|
||||
} else {
|
||||
$this->store($results);
|
||||
|
||||
$this->callAfterCreating($results, $parent);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models and persist them to the database.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function createQuietly($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
return Model::withoutEvents(function () use ($attributes, $parent) {
|
||||
return $this->create($attributes, $parent);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback that persists a model in the database when invoked.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Closure
|
||||
*/
|
||||
public function lazy(array $attributes = [], ?Model $parent = null)
|
||||
{
|
||||
return function () use ($attributes, $parent) {
|
||||
return $this->create($attributes, $parent);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection name on the results and store them.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @return void
|
||||
*/
|
||||
protected function store(Collection $results)
|
||||
{
|
||||
$results->each(function ($model) {
|
||||
if (! isset($this->connection)) {
|
||||
$model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName());
|
||||
}
|
||||
|
||||
$model->save();
|
||||
|
||||
$this->createChildren($model);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the children for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
protected function createChildren(Model $model)
|
||||
{
|
||||
Model::unguarded(function () use ($model) {
|
||||
$this->has->each(function ($has) use ($model) {
|
||||
$has->createFor($model);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a single instance of the model.
|
||||
*
|
||||
* @param callable|array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function makeOne($attributes = [])
|
||||
{
|
||||
return $this->count(null)->make($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collection of models.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function make($attributes = [], ?Model $parent = null)
|
||||
{
|
||||
if (! empty($attributes)) {
|
||||
return $this->state($attributes)->make([], $parent);
|
||||
}
|
||||
|
||||
if ($this->count === null) {
|
||||
return tap($this->makeInstance($parent), function ($instance) {
|
||||
$this->callAfterMaking(collect([$instance]));
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->count < 1) {
|
||||
return $this->newModel()->newCollection();
|
||||
}
|
||||
|
||||
$instances = $this->newModel()->newCollection(array_map(function () use ($parent) {
|
||||
return $this->makeInstance($parent);
|
||||
}, range(1, $this->count)));
|
||||
|
||||
$this->callAfterMaking($instances);
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an instance of the model with the given attributes.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected function makeInstance(?Model $parent)
|
||||
{
|
||||
return Model::unguarded(function () use ($parent) {
|
||||
return tap($this->newModel($this->getExpandedAttributes($parent)), function ($instance) {
|
||||
if (isset($this->connection)) {
|
||||
$instance->setConnection($this->connection);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a raw attributes array for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getExpandedAttributes(?Model $parent)
|
||||
{
|
||||
return $this->expandAttributes($this->getRawAttributes($parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw attributes for the model as an array.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return array
|
||||
*/
|
||||
protected function getRawAttributes(?Model $parent)
|
||||
{
|
||||
return $this->states->pipe(function ($states) {
|
||||
return $this->for->isEmpty() ? $states : new Collection(array_merge([function () {
|
||||
return $this->parentResolvers();
|
||||
}], $states->all()));
|
||||
})->reduce(function ($carry, $state) use ($parent) {
|
||||
if ($state instanceof Closure) {
|
||||
$state = $state->bindTo($this);
|
||||
}
|
||||
|
||||
return array_merge($carry, $state($carry, $parent));
|
||||
}, $this->definition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the parent relationship resolvers (as deferred Closures).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parentResolvers()
|
||||
{
|
||||
$model = $this->newModel();
|
||||
|
||||
return $this->for->map(function (BelongsToRelationship $for) use ($model) {
|
||||
return $for->attributesFor($model);
|
||||
})->collapse()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all attributes to their underlying values.
|
||||
*
|
||||
* @param array $definition
|
||||
* @return array
|
||||
*/
|
||||
protected function expandAttributes(array $definition)
|
||||
{
|
||||
return collect($definition)->map(function ($attribute, $key) use (&$definition) {
|
||||
if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
|
||||
$attribute = $attribute($definition);
|
||||
}
|
||||
|
||||
if ($attribute instanceof self) {
|
||||
$attribute = $attribute->create()->getKey();
|
||||
} elseif ($attribute instanceof Model) {
|
||||
$attribute = $attribute->getKey();
|
||||
}
|
||||
|
||||
$definition[$key] = $attribute;
|
||||
|
||||
return $attribute;
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new state transformation to the model definition.
|
||||
*
|
||||
* @param callable|array $state
|
||||
* @return static
|
||||
*/
|
||||
public function state($state)
|
||||
{
|
||||
return $this->newInstance([
|
||||
'states' => $this->states->concat([
|
||||
is_callable($state) ? $state : function () use ($state) {
|
||||
return $state;
|
||||
},
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sequenced state transformation to the model definition.
|
||||
*
|
||||
* @param array $sequence
|
||||
* @return static
|
||||
*/
|
||||
public function sequence(...$sequence)
|
||||
{
|
||||
return $this->state(new Sequence(...$sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a child relationship for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory $factory
|
||||
* @param string|null $relationship
|
||||
* @return static
|
||||
*/
|
||||
public function has(self $factory, $relationship = null)
|
||||
{
|
||||
return $this->newInstance([
|
||||
'has' => $this->has->concat([new Relationship(
|
||||
$factory, $relationship ?: $this->guessRelationship($factory->modelName())
|
||||
)]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to guess the relationship name for a "has" relationship.
|
||||
*
|
||||
* @param string $related
|
||||
* @return string
|
||||
*/
|
||||
protected function guessRelationship(string $related)
|
||||
{
|
||||
$guess = Str::camel(Str::plural(class_basename($related)));
|
||||
|
||||
return method_exists($this->modelName(), $guess) ? $guess : Str::singular($guess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an attached relationship for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model $factory
|
||||
* @param callable|array $pivot
|
||||
* @param string|null $relationship
|
||||
* @return static
|
||||
*/
|
||||
public function hasAttached($factory, $pivot = [], $relationship = null)
|
||||
{
|
||||
return $this->newInstance([
|
||||
'has' => $this->has->concat([new BelongsToManyRelationship(
|
||||
$factory,
|
||||
$pivot,
|
||||
$relationship ?: Str::camel(Str::plural(class_basename(
|
||||
$factory instanceof Factory
|
||||
? $factory->modelName()
|
||||
: Collection::wrap($factory)->first()
|
||||
)))
|
||||
)]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a parent relationship for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory
|
||||
* @param string|null $relationship
|
||||
* @return static
|
||||
*/
|
||||
public function for($factory, $relationship = null)
|
||||
{
|
||||
return $this->newInstance(['for' => $this->for->concat([new BelongsToRelationship(
|
||||
$factory,
|
||||
$relationship ?: Str::camel(class_basename(
|
||||
$factory instanceof Factory ? $factory->modelName() : $factory
|
||||
))
|
||||
)])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new "after making" callback to the model definition.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return static
|
||||
*/
|
||||
public function afterMaking(Closure $callback)
|
||||
{
|
||||
return $this->newInstance(['afterMaking' => $this->afterMaking->concat([$callback])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new "after creating" callback to the model definition.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return static
|
||||
*/
|
||||
public function afterCreating(Closure $callback)
|
||||
{
|
||||
return $this->newInstance(['afterCreating' => $this->afterCreating->concat([$callback])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the "after making" callbacks for the given model instances.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $instances
|
||||
* @return void
|
||||
*/
|
||||
protected function callAfterMaking(Collection $instances)
|
||||
{
|
||||
$instances->each(function ($model) {
|
||||
$this->afterMaking->each(function ($callback) use ($model) {
|
||||
$callback($model);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the "after creating" callbacks for the given model instances.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $instances
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $parent
|
||||
* @return void
|
||||
*/
|
||||
protected function callAfterCreating(Collection $instances, ?Model $parent = null)
|
||||
{
|
||||
$instances->each(function ($model) use ($parent) {
|
||||
$this->afterCreating->each(function ($callback) use ($model, $parent) {
|
||||
$callback($model, $parent);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how many models should be generated.
|
||||
*
|
||||
* @param int|null $count
|
||||
* @return static
|
||||
*/
|
||||
public function count(?int $count)
|
||||
{
|
||||
return $this->newInstance(['count' => $count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the database connection that should be used to generate models.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return static
|
||||
*/
|
||||
public function connection(string $connection)
|
||||
{
|
||||
return $this->newInstance(['connection' => $connection]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the factory builder with the given mutated properties.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return static
|
||||
*/
|
||||
protected function newInstance(array $arguments = [])
|
||||
{
|
||||
return new static(...array_values(array_merge([
|
||||
'count' => $this->count,
|
||||
'states' => $this->states,
|
||||
'has' => $this->has,
|
||||
'for' => $this->for,
|
||||
'afterMaking' => $this->afterMaking,
|
||||
'afterCreating' => $this->afterCreating,
|
||||
'connection' => $this->connection,
|
||||
], $arguments)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new model instance.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function newModel(array $attributes = [])
|
||||
{
|
||||
$model = $this->modelName();
|
||||
|
||||
return new $model($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the model that is generated by the factory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function modelName()
|
||||
{
|
||||
$resolver = static::$modelNameResolver ?: function (self $factory) {
|
||||
$factoryBasename = Str::replaceLast('Factory', '', class_basename($factory));
|
||||
|
||||
$appNamespace = static::appNamespace();
|
||||
|
||||
return class_exists($appNamespace.'Models\\'.$factoryBasename)
|
||||
? $appNamespace.'Models\\'.$factoryBasename
|
||||
: $appNamespace.$factoryBasename;
|
||||
};
|
||||
|
||||
return $this->model ?: $resolver($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the callback that should be invoked to guess model names based on factory names.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function guessModelNamesUsing(callable $callback)
|
||||
{
|
||||
static::$modelNameResolver = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the default namespace that contains the application's model factories.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return void
|
||||
*/
|
||||
public static function useNamespace(string $namespace)
|
||||
{
|
||||
static::$namespace = $namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new factory instance for the given model name.
|
||||
*
|
||||
* @param string $modelName
|
||||
* @return static
|
||||
*/
|
||||
public static function factoryForModel(string $modelName)
|
||||
{
|
||||
$factory = static::resolveFactoryName($modelName);
|
||||
|
||||
return $factory::new();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the callback that should be invoked to guess factory names based on dynamic relationship names.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function guessFactoryNamesUsing(callable $callback)
|
||||
{
|
||||
static::$factoryNameResolver = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new Faker instance.
|
||||
*
|
||||
* @return \Faker\Generator
|
||||
*/
|
||||
protected function withFaker()
|
||||
{
|
||||
return Container::getInstance()->make(Generator::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory name for the given model name.
|
||||
*
|
||||
* @param string $modelName
|
||||
* @return string
|
||||
*/
|
||||
public static function resolveFactoryName(string $modelName)
|
||||
{
|
||||
$resolver = static::$factoryNameResolver ?: function (string $modelName) {
|
||||
$appNamespace = static::appNamespace();
|
||||
|
||||
$modelName = Str::startsWith($modelName, $appNamespace.'Models\\')
|
||||
? Str::after($modelName, $appNamespace.'Models\\')
|
||||
: Str::after($modelName, $appNamespace);
|
||||
|
||||
return static::$namespace.$modelName.'Factory';
|
||||
};
|
||||
|
||||
return $resolver($modelName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application namespace for the application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function appNamespace()
|
||||
{
|
||||
try {
|
||||
return Container::getInstance()
|
||||
->make(Application::class)
|
||||
->getNamespace();
|
||||
} catch (Throwable $e) {
|
||||
return 'App\\';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy dynamic factory methods onto their proper methods.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (static::hasMacro($method)) {
|
||||
return $this->macroCall($method, $parameters);
|
||||
}
|
||||
|
||||
if (! Str::startsWith($method, ['for', 'has'])) {
|
||||
static::throwBadMethodCallException($method);
|
||||
}
|
||||
|
||||
$relationship = Str::camel(Str::substr($method, 3));
|
||||
|
||||
$relatedModel = get_class($this->newModel()->{$relationship}()->getRelated());
|
||||
|
||||
if (method_exists($relatedModel, 'newFactory')) {
|
||||
$factory = $relatedModel::newFactory() ?: static::factoryForModel($relatedModel);
|
||||
} else {
|
||||
$factory = static::factoryForModel($relatedModel);
|
||||
}
|
||||
|
||||
if (Str::startsWith($method, 'for')) {
|
||||
return $this->for($factory->state($parameters[0] ?? []), $relationship);
|
||||
} elseif (Str::startsWith($method, 'has')) {
|
||||
return $this->has(
|
||||
$factory
|
||||
->count(is_numeric($parameters[0] ?? null) ? $parameters[0] : 1)
|
||||
->state((is_callable($parameters[0] ?? null) || is_array($parameters[0] ?? null)) ? $parameters[0] : ($parameters[1] ?? [])),
|
||||
$relationship
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
trait HasFactory
|
||||
{
|
||||
/**
|
||||
* Get a new factory instance for the model.
|
||||
*
|
||||
* @param mixed $parameters
|
||||
* @return \Illuminate\Database\Eloquent\Factories\Factory
|
||||
*/
|
||||
public static function factory(...$parameters)
|
||||
{
|
||||
$factory = static::newFactory() ?: Factory::factoryForModel(get_called_class());
|
||||
|
||||
return $factory
|
||||
->count(is_numeric($parameters[0] ?? null) ? $parameters[0] : null)
|
||||
->state(is_array($parameters[0] ?? null) ? $parameters[0] : ($parameters[1] ?? []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new factory instance for the model.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Factories\Factory
|
||||
*/
|
||||
protected static function newFactory()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
|
||||
|
||||
class Relationship
|
||||
{
|
||||
/**
|
||||
* The related factory instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Factories\Factory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
* The relationship name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* Create a new child relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Factories\Factory $factory
|
||||
* @param string $relationship
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Factory $factory, $relationship)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->relationship = $relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the child relationship for the given parent model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return void
|
||||
*/
|
||||
public function createFor(Model $parent)
|
||||
{
|
||||
$relationship = $parent->{$this->relationship}();
|
||||
|
||||
if ($relationship instanceof MorphOneOrMany) {
|
||||
$this->factory->state([
|
||||
$relationship->getMorphType() => $relationship->getMorphClass(),
|
||||
$relationship->getForeignKeyName() => $relationship->getParentKey(),
|
||||
])->create([], $parent);
|
||||
} elseif ($relationship instanceof HasOneOrMany) {
|
||||
$this->factory->state([
|
||||
$relationship->getForeignKeyName() => $relationship->getParentKey(),
|
||||
])->create([], $parent);
|
||||
} elseif ($relationship instanceof BelongsToMany) {
|
||||
$relationship->attach($this->factory->create([], $parent));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Factories;
|
||||
|
||||
class Sequence
|
||||
{
|
||||
/**
|
||||
* The sequence of return values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sequence;
|
||||
|
||||
/**
|
||||
* The count of the sequence items.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $count;
|
||||
|
||||
/**
|
||||
* The current index of the sequence iteration.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $index = 0;
|
||||
|
||||
/**
|
||||
* Create a new sequence instance.
|
||||
*
|
||||
* @param array $sequence
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(...$sequence)
|
||||
{
|
||||
$this->sequence = $sequence;
|
||||
$this->count = count($sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next value in the sequence.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
return tap(value($this->sequence[$this->index % $this->count], $this), function () {
|
||||
$this->index = $this->index + 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
class HigherOrderBuilderProxy
|
||||
{
|
||||
/**
|
||||
* The collection being operated on.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* The method being proxied.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $method;
|
||||
|
||||
/**
|
||||
* Create a new proxy instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @param string $method
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $builder, $method)
|
||||
{
|
||||
$this->method = $method;
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy a scope call onto the query builder.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
return $this->builder->{$this->method}(function ($value) use ($method, $parameters) {
|
||||
return $value->{$method}(...$parameters);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class InvalidCastException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* The name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* The name of the column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $column;
|
||||
|
||||
/**
|
||||
* The name of the cast type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $castType;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param object $model
|
||||
* @param string $column
|
||||
* @param string $castType
|
||||
* @return static
|
||||
*/
|
||||
public function __construct($model, $column, $castType)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
parent::__construct("Call to undefined cast [{$castType}] on column [{$column}] in model [{$class}].");
|
||||
|
||||
$this->model = $class;
|
||||
$this->column = $column;
|
||||
$this->castType = $castType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class JsonEncodingException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Create a new JSON encoding exception for the model.
|
||||
*
|
||||
* @param mixed $model
|
||||
* @param string $message
|
||||
* @return static
|
||||
*/
|
||||
public static function forModel($model, $message)
|
||||
{
|
||||
return new static('Error encoding model ['.get_class($model).'] with ID ['.$model->getKey().'] to JSON: '.$message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JSON encoding exception for the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Resources\Json\JsonResource $resource
|
||||
* @param string $message
|
||||
* @return static
|
||||
*/
|
||||
public static function forResource($resource, $message)
|
||||
{
|
||||
$model = $resource->resource;
|
||||
|
||||
return new static('Error encoding resource ['.get_class($resource).'] with model ['.get_class($model).'] with ID ['.$model->getKey().'] to JSON: '.$message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JSON encoding exception for an attribute.
|
||||
*
|
||||
* @param mixed $model
|
||||
* @param mixed $key
|
||||
* @param string $message
|
||||
* @return static
|
||||
*/
|
||||
public static function forAttribute($model, $key, $message)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
return new static("Unable to encode attribute [{$key}] for model [{$class}] to JSON: {$message}.");
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class MassAssignmentException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use LogicException;
|
||||
|
||||
trait MassPrunable
|
||||
{
|
||||
/**
|
||||
* Prune all prunable models in the database.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return int
|
||||
*/
|
||||
public function pruneAll(int $chunkSize = 1000)
|
||||
{
|
||||
$query = tap($this->prunable(), function ($query) use ($chunkSize) {
|
||||
$query->when(! $query->getQuery()->limit, function ($query) use ($chunkSize) {
|
||||
$query->limit($chunkSize);
|
||||
});
|
||||
});
|
||||
|
||||
$total = 0;
|
||||
|
||||
do {
|
||||
$total += $count = in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))
|
||||
? $query->forceDelete()
|
||||
: $query->delete();
|
||||
|
||||
if ($count > 0) {
|
||||
event(new ModelsPruned(static::class, $total));
|
||||
}
|
||||
} while ($count > 0);
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prunable model query.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function prunable()
|
||||
{
|
||||
throw new LogicException('Please implement the prunable method on your model.');
|
||||
}
|
||||
}
|
||||
+2159
@@ -0,0 +1,2159 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use ArrayAccess;
|
||||
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
|
||||
use Illuminate\Contracts\Queue\QueueableCollection;
|
||||
use Illuminate\Contracts\Queue\QueueableEntity;
|
||||
use Illuminate\Contracts\Routing\UrlRoutable;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Jsonable;
|
||||
use Illuminate\Database\ConnectionResolverInterface as Resolver;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use JsonSerializable;
|
||||
use LogicException;
|
||||
|
||||
abstract class Model implements Arrayable, ArrayAccess, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
|
||||
{
|
||||
use Concerns\HasAttributes,
|
||||
Concerns\HasEvents,
|
||||
Concerns\HasGlobalScopes,
|
||||
Concerns\HasRelationships,
|
||||
Concerns\HasTimestamps,
|
||||
Concerns\HidesAttributes,
|
||||
Concerns\GuardsAttributes,
|
||||
ForwardsCalls;
|
||||
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* The primary key for the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* The "type" of the primary key ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $keyType = 'int';
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $incrementing = true;
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = [];
|
||||
|
||||
/**
|
||||
* The relationship counts that should be eager loaded on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $withCount = [];
|
||||
|
||||
/**
|
||||
* Indicates whether lazy loading will be prevented on this model.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $preventsLazyLoading = false;
|
||||
|
||||
/**
|
||||
* The number of models to return for pagination.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $perPage = 15;
|
||||
|
||||
/**
|
||||
* Indicates if the model exists.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $exists = false;
|
||||
|
||||
/**
|
||||
* Indicates if the model was inserted during the current request lifecycle.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $wasRecentlyCreated = false;
|
||||
|
||||
/**
|
||||
* The connection resolver instance.
|
||||
*
|
||||
* @var \Illuminate\Database\ConnectionResolverInterface
|
||||
*/
|
||||
protected static $resolver;
|
||||
|
||||
/**
|
||||
* The event dispatcher instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected static $dispatcher;
|
||||
|
||||
/**
|
||||
* The array of booted models.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $booted = [];
|
||||
|
||||
/**
|
||||
* The array of trait initializers that will be called on each new instance.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $traitInitializers = [];
|
||||
|
||||
/**
|
||||
* The array of global scopes on the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $globalScopes = [];
|
||||
|
||||
/**
|
||||
* The list of models classes that should not be affected with touch.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $ignoreOnTouch = [];
|
||||
|
||||
/**
|
||||
* Indicates whether lazy loading should be restricted on all models.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $modelsShouldPreventLazyLoading = false;
|
||||
|
||||
/**
|
||||
* The callback that is responsible for handling lazy loading violations.
|
||||
*
|
||||
* @var callable|null
|
||||
*/
|
||||
protected static $lazyLoadingViolationCallback;
|
||||
|
||||
/**
|
||||
* Indicates if broadcasting is currently enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $isBroadcasting = true;
|
||||
|
||||
/**
|
||||
* The name of the "created at" column.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
const CREATED_AT = 'created_at';
|
||||
|
||||
/**
|
||||
* The name of the "updated at" column.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
const UPDATED_AT = 'updated_at';
|
||||
|
||||
/**
|
||||
* Create a new Eloquent model instance.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->bootIfNotBooted();
|
||||
|
||||
$this->initializeTraits();
|
||||
|
||||
$this->syncOriginal();
|
||||
|
||||
$this->fill($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model needs to be booted and if so, do it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function bootIfNotBooted()
|
||||
{
|
||||
if (! isset(static::$booted[static::class])) {
|
||||
static::$booted[static::class] = true;
|
||||
|
||||
$this->fireModelEvent('booting', false);
|
||||
|
||||
static::booting();
|
||||
static::boot();
|
||||
static::booted();
|
||||
|
||||
$this->fireModelEvent('booted', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required before the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booting()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap the model and its traits.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
static::bootTraits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot all of the bootable traits on the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function bootTraits()
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
$booted = [];
|
||||
|
||||
static::$traitInitializers[$class] = [];
|
||||
|
||||
foreach (class_uses_recursive($class) as $trait) {
|
||||
$method = 'boot'.class_basename($trait);
|
||||
|
||||
if (method_exists($class, $method) && ! in_array($method, $booted)) {
|
||||
forward_static_call([$class, $method]);
|
||||
|
||||
$booted[] = $method;
|
||||
}
|
||||
|
||||
if (method_exists($class, $method = 'initialize'.class_basename($trait))) {
|
||||
static::$traitInitializers[$class][] = $method;
|
||||
|
||||
static::$traitInitializers[$class] = array_unique(
|
||||
static::$traitInitializers[$class]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize any initializable traits on the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initializeTraits()
|
||||
{
|
||||
foreach (static::$traitInitializers[static::class] as $method) {
|
||||
$this->{$method}();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the list of booted models so they will be re-booted.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearBootedModels()
|
||||
{
|
||||
static::$booted = [];
|
||||
|
||||
static::$globalScopes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables relationship model touching for the current class during given callback scope.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function withoutTouching(callable $callback)
|
||||
{
|
||||
static::withoutTouchingOn([static::class], $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables relationship model touching for the given model classes during given callback scope.
|
||||
*
|
||||
* @param array $models
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function withoutTouchingOn(array $models, callable $callback)
|
||||
{
|
||||
static::$ignoreOnTouch = array_values(array_merge(static::$ignoreOnTouch, $models));
|
||||
|
||||
try {
|
||||
$callback();
|
||||
} finally {
|
||||
static::$ignoreOnTouch = array_values(array_diff(static::$ignoreOnTouch, $models));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given model is ignoring touches.
|
||||
*
|
||||
* @param string|null $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function isIgnoringTouch($class = null)
|
||||
{
|
||||
$class = $class ?: static::class;
|
||||
|
||||
if (! get_class_vars($class)['timestamps'] || ! $class::UPDATED_AT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (static::$ignoreOnTouch as $ignoredClass) {
|
||||
if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent model relationships from being lazy loaded.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return void
|
||||
*/
|
||||
public static function preventLazyLoading($value = true)
|
||||
{
|
||||
static::$modelsShouldPreventLazyLoading = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that is responsible for handling lazy loading violations.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function handleLazyLoadingViolationUsing(callable $callback)
|
||||
{
|
||||
static::$lazyLoadingViolationCallback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback without broadcasting any model events for all model types.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutBroadcasting(callable $callback)
|
||||
{
|
||||
$isBroadcasting = static::$isBroadcasting;
|
||||
|
||||
static::$isBroadcasting = false;
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::$isBroadcasting = $isBroadcasting;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the model with an array of attributes.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\MassAssignmentException
|
||||
*/
|
||||
public function fill(array $attributes)
|
||||
{
|
||||
$totallyGuarded = $this->totallyGuarded();
|
||||
|
||||
foreach ($this->fillableFromArray($attributes) as $key => $value) {
|
||||
// The developers may choose to place some attributes in the "fillable" array
|
||||
// which means only those attributes may be set through mass assignment to
|
||||
// the model, and all others will just get ignored for security reasons.
|
||||
if ($this->isFillable($key)) {
|
||||
$this->setAttribute($key, $value);
|
||||
} elseif ($totallyGuarded) {
|
||||
throw new MassAssignmentException(sprintf(
|
||||
'Add [%s] to fillable property to allow mass assignment on [%s].',
|
||||
$key, get_class($this)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the model with an array of attributes. Force mass assignment.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return $this
|
||||
*/
|
||||
public function forceFill(array $attributes)
|
||||
{
|
||||
return static::unguarded(function () use ($attributes) {
|
||||
return $this->fill($attributes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify the given column name by the model's table.
|
||||
*
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
public function qualifyColumn($column)
|
||||
{
|
||||
if (Str::contains($column, '.')) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
return $this->getTable().'.'.$column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify the given columns with the model's table.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return array
|
||||
*/
|
||||
public function qualifyColumns($columns)
|
||||
{
|
||||
return collect($columns)->map(function ($column) {
|
||||
return $this->qualifyColumn($column);
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the given model.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param bool $exists
|
||||
* @return static
|
||||
*/
|
||||
public function newInstance($attributes = [], $exists = false)
|
||||
{
|
||||
// This method just provides a convenient way for us to generate fresh model
|
||||
// instances of this current model. It is particularly useful during the
|
||||
// hydration of new objects via the Eloquent query builder instances.
|
||||
$model = new static((array) $attributes);
|
||||
|
||||
$model->exists = $exists;
|
||||
|
||||
$model->setConnection(
|
||||
$this->getConnectionName()
|
||||
);
|
||||
|
||||
$model->setTable($this->getTable());
|
||||
|
||||
$model->mergeCasts($this->casts);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance that is existing.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param string|null $connection
|
||||
* @return static
|
||||
*/
|
||||
public function newFromBuilder($attributes = [], $connection = null)
|
||||
{
|
||||
$model = $this->newInstance([], true);
|
||||
|
||||
$model->setRawAttributes((array) $attributes, true);
|
||||
|
||||
$model->setConnection($connection ?: $this->getConnectionName());
|
||||
|
||||
$model->fireModelEvent('retrieved', false);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin querying the model on a given connection.
|
||||
*
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public static function on($connection = null)
|
||||
{
|
||||
// First we will just create a fresh instance of this model, and then we can set the
|
||||
// connection on the model so that it is used for the queries we execute, as well
|
||||
// as being set on every relation we retrieve without a custom connection name.
|
||||
$instance = new static;
|
||||
|
||||
$instance->setConnection($connection);
|
||||
|
||||
return $instance->newQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin querying the model on the write connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public static function onWriteConnection()
|
||||
{
|
||||
return static::query()->useWritePdo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the models from the database.
|
||||
*
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection|static[]
|
||||
*/
|
||||
public static function all($columns = ['*'])
|
||||
{
|
||||
return static::query()->get(
|
||||
is_array($columns) ? $columns : func_get_args()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin querying a model with eager loading.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public static function with($relations)
|
||||
{
|
||||
return static::query()->with(
|
||||
is_string($relations) ? func_get_args() : $relations
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relations on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function load($relations)
|
||||
{
|
||||
$query = $this->newQueryWithoutRelationships()->with(
|
||||
is_string($relations) ? func_get_args() : $relations
|
||||
);
|
||||
|
||||
$query->eagerLoadRelations([$this]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationships on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorph($relation, $relations)
|
||||
{
|
||||
if (! $this->{$relation}) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$className = get_class($this->{$relation});
|
||||
|
||||
$this->{$relation}->load($relations[$className] ?? []);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relations on the model if they are not already eager loaded.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMissing($relations)
|
||||
{
|
||||
$relations = is_string($relations) ? func_get_args() : $relations;
|
||||
|
||||
$this->newCollection([$this])->loadMissing($relations);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relation's column aggregations on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @param string $column
|
||||
* @param string $function
|
||||
* @return $this
|
||||
*/
|
||||
public function loadAggregate($relations, $column, $function = null)
|
||||
{
|
||||
$this->newCollection([$this])->loadAggregate($relations, $column, $function);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relation counts on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadCount($relations)
|
||||
{
|
||||
$relations = is_string($relations) ? func_get_args() : $relations;
|
||||
|
||||
return $this->loadAggregate($relations, '*', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relation max column values on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMax($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'max');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relation min column values on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMin($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relation's column summations on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadSum($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'sum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relation average column values on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadAvg($relations, $column)
|
||||
{
|
||||
return $this->loadAggregate($relations, $column, 'avg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load related model existence values on the model.
|
||||
*
|
||||
* @param array|string $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadExists($relations)
|
||||
{
|
||||
return $this->loadAggregate($relations, '*', 'exists');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationship column aggregation on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @param string $column
|
||||
* @param string $function
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphAggregate($relation, $relations, $column, $function = null)
|
||||
{
|
||||
if (! $this->{$relation}) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$className = get_class($this->{$relation});
|
||||
|
||||
$this->{$relation}->loadAggregate($relations[$className] ?? [], $column, $function);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationship counts on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphCount($relation, $relations)
|
||||
{
|
||||
return $this->loadMorphAggregate($relation, $relations, '*', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationship max column values on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphMax($relation, $relations, $column)
|
||||
{
|
||||
return $this->loadMorphAggregate($relation, $relations, $column, 'max');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationship min column values on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphMin($relation, $relations, $column)
|
||||
{
|
||||
return $this->loadMorphAggregate($relation, $relations, $column, 'min');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationship column summations on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphSum($relation, $relations, $column)
|
||||
{
|
||||
return $this->loadMorphAggregate($relation, $relations, $column, 'sum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager load relationship average column values on the polymorphic relation of a model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param array $relations
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function loadMorphAvg($relation, $relations, $column)
|
||||
{
|
||||
return $this->loadMorphAggregate($relation, $relations, $column, 'avg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a column's value by a given amount.
|
||||
*
|
||||
* @param string $column
|
||||
* @param float|int $amount
|
||||
* @param array $extra
|
||||
* @return int
|
||||
*/
|
||||
protected function increment($column, $amount = 1, array $extra = [])
|
||||
{
|
||||
return $this->incrementOrDecrement($column, $amount, $extra, 'increment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement a column's value by a given amount.
|
||||
*
|
||||
* @param string $column
|
||||
* @param float|int $amount
|
||||
* @param array $extra
|
||||
* @return int
|
||||
*/
|
||||
protected function decrement($column, $amount = 1, array $extra = [])
|
||||
{
|
||||
return $this->incrementOrDecrement($column, $amount, $extra, 'decrement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the increment or decrement method on the model.
|
||||
*
|
||||
* @param string $column
|
||||
* @param float|int $amount
|
||||
* @param array $extra
|
||||
* @param string $method
|
||||
* @return int
|
||||
*/
|
||||
protected function incrementOrDecrement($column, $amount, $extra, $method)
|
||||
{
|
||||
$query = $this->newQueryWithoutRelationships();
|
||||
|
||||
if (! $this->exists) {
|
||||
return $query->{$method}($column, $amount, $extra);
|
||||
}
|
||||
|
||||
$this->{$column} = $this->isClassDeviable($column)
|
||||
? $this->deviateClassCastableAttribute($method, $column, $amount)
|
||||
: $this->{$column} + ($method === 'increment' ? $amount : $amount * -1);
|
||||
|
||||
$this->forceFill($extra);
|
||||
|
||||
if ($this->fireModelEvent('updating') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return tap($this->setKeysForSaveQuery($query)->{$method}($column, $amount, $extra), function () use ($column) {
|
||||
$this->syncChanges();
|
||||
|
||||
$this->fireModelEvent('updated', false);
|
||||
|
||||
$this->syncOriginalAttribute($column);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model in the database.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function update(array $attributes = [], array $options = [])
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->fill($attributes)->save($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model in the database within a transaction.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function updateOrFail(array $attributes = [], array $options = [])
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->fill($attributes)->saveOrFail($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model in the database without raising any events.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function updateQuietly(array $attributes = [], array $options = [])
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->fill($attributes)->saveQuietly($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model and all of its relationships.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function push()
|
||||
{
|
||||
if (! $this->save()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// To sync all of the relationships to the database, we will simply spin through
|
||||
// the relationships and save each model via this "push" method, which allows
|
||||
// us to recurse into all of these nested relations for the model instance.
|
||||
foreach ($this->relations as $models) {
|
||||
$models = $models instanceof Collection
|
||||
? $models->all() : [$models];
|
||||
|
||||
foreach (array_filter($models) as $model) {
|
||||
if (! $model->push()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model to the database without raising any events.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function saveQuietly(array $options = [])
|
||||
{
|
||||
return static::withoutEvents(function () use ($options) {
|
||||
return $this->save($options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model to the database.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function save(array $options = [])
|
||||
{
|
||||
$this->mergeAttributesFromClassCasts();
|
||||
|
||||
$query = $this->newModelQuery();
|
||||
|
||||
// If the "saving" event returns false we'll bail out of the save and return
|
||||
// false, indicating that the save failed. This provides a chance for any
|
||||
// listeners to cancel save operations if validations fail or whatever.
|
||||
if ($this->fireModelEvent('saving') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the model already exists in the database we can just update our record
|
||||
// that is already in this database using the current IDs in this "where"
|
||||
// clause to only update this model. Otherwise, we'll just insert them.
|
||||
if ($this->exists) {
|
||||
$saved = $this->isDirty() ?
|
||||
$this->performUpdate($query) : true;
|
||||
}
|
||||
|
||||
// If the model is brand new, we'll insert it into our database and set the
|
||||
// ID attribute on the model to the value of the newly inserted row's ID
|
||||
// which is typically an auto-increment value managed by the database.
|
||||
else {
|
||||
$saved = $this->performInsert($query);
|
||||
|
||||
if (! $this->getConnectionName() &&
|
||||
$connection = $query->getConnection()) {
|
||||
$this->setConnection($connection->getName());
|
||||
}
|
||||
}
|
||||
|
||||
// If the model is successfully saved, we need to do a few more things once
|
||||
// that is done. We will call the "saved" method here to run any actions
|
||||
// we need to happen after a model gets successfully saved right here.
|
||||
if ($saved) {
|
||||
$this->finishSave($options);
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the model to the database within a transaction.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function saveOrFail(array $options = [])
|
||||
{
|
||||
return $this->getConnection()->transaction(function () use ($options) {
|
||||
return $this->save($options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions that are necessary after the model is saved.
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
protected function finishSave(array $options)
|
||||
{
|
||||
$this->fireModelEvent('saved', false);
|
||||
|
||||
if ($this->isDirty() && ($options['touch'] ?? true)) {
|
||||
$this->touchOwners();
|
||||
}
|
||||
|
||||
$this->syncOriginal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a model update operation.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return bool
|
||||
*/
|
||||
protected function performUpdate(Builder $query)
|
||||
{
|
||||
// If the updating event returns false, we will cancel the update operation so
|
||||
// developers can hook Validation systems into their models and cancel this
|
||||
// operation if the model does not pass validation. Otherwise, we update.
|
||||
if ($this->fireModelEvent('updating') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First we need to create a fresh query instance and touch the creation and
|
||||
// update timestamp on the model which are maintained by us for developer
|
||||
// convenience. Then we will just continue saving the model instances.
|
||||
if ($this->usesTimestamps()) {
|
||||
$this->updateTimestamps();
|
||||
}
|
||||
|
||||
// Once we have run the update operation, we will fire the "updated" event for
|
||||
// this model instance. This will allow developers to hook into these after
|
||||
// models are updated, giving them a chance to do any special processing.
|
||||
$dirty = $this->getDirty();
|
||||
|
||||
if (count($dirty) > 0) {
|
||||
$this->setKeysForSaveQuery($query)->update($dirty);
|
||||
|
||||
$this->syncChanges();
|
||||
|
||||
$this->fireModelEvent('updated', false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a select query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSelectQuery($query)
|
||||
{
|
||||
$query->where($this->getKeyName(), '=', $this->getKeyForSelectQuery());
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary key value for a select query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getKeyForSelectQuery()
|
||||
{
|
||||
return $this->original[$this->getKeyName()] ?? $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a save update query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSaveQuery($query)
|
||||
{
|
||||
$query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery());
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary key value for a save query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getKeyForSaveQuery()
|
||||
{
|
||||
return $this->original[$this->getKeyName()] ?? $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a model insert operation.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return bool
|
||||
*/
|
||||
protected function performInsert(Builder $query)
|
||||
{
|
||||
if ($this->fireModelEvent('creating') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First we'll need to create a fresh query instance and touch the creation and
|
||||
// update timestamps on this model, which are maintained by us for developer
|
||||
// convenience. After, we will just continue saving these model instances.
|
||||
if ($this->usesTimestamps()) {
|
||||
$this->updateTimestamps();
|
||||
}
|
||||
|
||||
// If the model has an incrementing key, we can use the "insertGetId" method on
|
||||
// the query builder, which will give us back the final inserted ID for this
|
||||
// table from the database. Not all tables have to be incrementing though.
|
||||
$attributes = $this->getAttributesForInsert();
|
||||
|
||||
if ($this->getIncrementing()) {
|
||||
$this->insertAndSetId($query, $attributes);
|
||||
}
|
||||
|
||||
// If the table isn't incrementing we'll simply insert these attributes as they
|
||||
// are. These attribute arrays must contain an "id" column previously placed
|
||||
// there by the developer as the manually determined key for these models.
|
||||
else {
|
||||
if (empty($attributes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$query->insert($attributes);
|
||||
}
|
||||
|
||||
// We will go ahead and set the exists property to true, so that it is set when
|
||||
// the created event is fired, just in case the developer tries to update it
|
||||
// during the event. This will allow them to do so and run an update here.
|
||||
$this->exists = true;
|
||||
|
||||
$this->wasRecentlyCreated = true;
|
||||
|
||||
$this->fireModelEvent('created', false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given attributes and set the ID on the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function insertAndSetId(Builder $query, $attributes)
|
||||
{
|
||||
$id = $query->insertGetId($attributes, $keyName = $this->getKeyName());
|
||||
|
||||
$this->setAttribute($keyName, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the models for the given IDs.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array|int|string $ids
|
||||
* @return int
|
||||
*/
|
||||
public static function destroy($ids)
|
||||
{
|
||||
if ($ids instanceof EloquentCollection) {
|
||||
$ids = $ids->modelKeys();
|
||||
}
|
||||
|
||||
if ($ids instanceof BaseCollection) {
|
||||
$ids = $ids->all();
|
||||
}
|
||||
|
||||
$ids = is_array($ids) ? $ids : func_get_args();
|
||||
|
||||
if (count($ids) === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We will actually pull the models from the database table and call delete on
|
||||
// each of them individually so that their events get fired properly with a
|
||||
// correct set of attributes in case the developers wants to check these.
|
||||
$key = ($instance = new static)->getKeyName();
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($instance->whereIn($key, $ids)->get() as $model) {
|
||||
if ($model->delete()) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the model from the database.
|
||||
*
|
||||
* @return bool|null
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->mergeAttributesFromClassCasts();
|
||||
|
||||
if (is_null($this->getKeyName())) {
|
||||
throw new LogicException('No primary key defined on model.');
|
||||
}
|
||||
|
||||
// If the model doesn't exist, there is nothing to delete so we'll just return
|
||||
// immediately and not do anything else. Otherwise, we will continue with a
|
||||
// deletion process on the model, firing the proper events, and so forth.
|
||||
if (! $this->exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fireModelEvent('deleting') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here, we'll touch the owning models, verifying these timestamps get updated
|
||||
// for the models. This will allow any caching to get broken on the parents
|
||||
// by the timestamp. Then we will go ahead and delete the model instance.
|
||||
$this->touchOwners();
|
||||
|
||||
$this->performDeleteOnModel();
|
||||
|
||||
// Once the model has been deleted, we will fire off the deleted event so that
|
||||
// the developers may hook into post-delete operations. We will then return
|
||||
// a boolean true as the delete is presumably successful on the database.
|
||||
$this->fireModelEvent('deleted', false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the model from the database within a transaction.
|
||||
*
|
||||
* @return bool|null
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function deleteOrFail()
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getConnection()->transaction(function () {
|
||||
return $this->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a hard delete on a soft deleted model.
|
||||
*
|
||||
* This method protects developers from running forceDelete when the trait is missing.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function forceDelete()
|
||||
{
|
||||
return $this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual delete query on this model instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function performDeleteOnModel()
|
||||
{
|
||||
$this->setKeysForSaveQuery($this->newModelQuery())->delete();
|
||||
|
||||
$this->exists = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin querying the model.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public static function query()
|
||||
{
|
||||
return (new static)->newQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query builder for the model's table.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newQuery()
|
||||
{
|
||||
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query builder that doesn't have any global scopes or eager loading.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function newModelQuery()
|
||||
{
|
||||
return $this->newEloquentBuilder(
|
||||
$this->newBaseQueryBuilder()
|
||||
)->setModel($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query builder with no relationships loaded.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newQueryWithoutRelationships()
|
||||
{
|
||||
return $this->registerGlobalScopes($this->newModelQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the global scopes for this builder instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function registerGlobalScopes($builder)
|
||||
{
|
||||
foreach ($this->getGlobalScopes() as $identifier => $scope) {
|
||||
$builder->withGlobalScope($identifier, $scope);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query builder that doesn't have any global scopes.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function newQueryWithoutScopes()
|
||||
{
|
||||
return $this->newModelQuery()
|
||||
->with($this->with)
|
||||
->withCount($this->withCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query instance without a given scope.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Scope|string $scope
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newQueryWithoutScope($scope)
|
||||
{
|
||||
return $this->newQuery()->withoutGlobalScope($scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query to restore one or more models by their queueable IDs.
|
||||
*
|
||||
* @param array|int $ids
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newQueryForRestoration($ids)
|
||||
{
|
||||
return is_array($ids)
|
||||
? $this->newQueryWithoutScopes()->whereIn($this->getQualifiedKeyName(), $ids)
|
||||
: $this->newQueryWithoutScopes()->whereKey($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Eloquent query builder for the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder|static
|
||||
*/
|
||||
public function newEloquentBuilder($query)
|
||||
{
|
||||
return new Builder($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query builder instance for the connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
protected function newBaseQueryBuilder()
|
||||
{
|
||||
return $this->getConnection()->query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Eloquent Collection instance.
|
||||
*
|
||||
* @param array $models
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function newCollection(array $models = [])
|
||||
{
|
||||
return new Collection($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pivot model instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param array $attributes
|
||||
* @param string $table
|
||||
* @param bool $exists
|
||||
* @param string|null $using
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Pivot
|
||||
*/
|
||||
public function newPivot(self $parent, array $attributes, $table, $exists, $using = null)
|
||||
{
|
||||
return $using ? $using::fromRawAttributes($parent, $attributes, $table, $exists)
|
||||
: Pivot::fromAttributes($parent, $attributes, $table, $exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has a given scope.
|
||||
*
|
||||
* @param string $scope
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNamedScope($scope)
|
||||
{
|
||||
return method_exists($this, 'scope'.ucfirst($scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given named scope if possible.
|
||||
*
|
||||
* @param string $scope
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function callNamedScope($scope, array $parameters = [])
|
||||
{
|
||||
return $this->{'scope'.ucfirst($scope)}(...$parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the model instance to an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return array_merge($this->attributesToArray(), $this->relationsToArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the model instance to JSON.
|
||||
*
|
||||
* @param int $options
|
||||
* @return string
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\JsonEncodingException
|
||||
*/
|
||||
public function toJson($options = 0)
|
||||
{
|
||||
$json = json_encode($this->jsonSerialize(), $options);
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw JsonEncodingException::forModel($this, json_last_error_msg());
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object into something JSON serializable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload a fresh model instance from the database.
|
||||
*
|
||||
* @param array|string $with
|
||||
* @return static|null
|
||||
*/
|
||||
public function fresh($with = [])
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->setKeysForSelectQuery($this->newQueryWithoutScopes())
|
||||
->with(is_string($with) ? func_get_args() : $with)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the current model instance with fresh attributes from the database.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->setRawAttributes(
|
||||
$this->setKeysForSelectQuery($this->newQueryWithoutScopes())->firstOrFail()->attributes
|
||||
);
|
||||
|
||||
$this->load(collect($this->relations)->reject(function ($relation) {
|
||||
return $relation instanceof Pivot
|
||||
|| (is_object($relation) && in_array(AsPivot::class, class_uses_recursive($relation), true));
|
||||
})->keys()->all());
|
||||
|
||||
$this->syncOriginal();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the model into a new, non-existing instance.
|
||||
*
|
||||
* @param array|null $except
|
||||
* @return static
|
||||
*/
|
||||
public function replicate(array $except = null)
|
||||
{
|
||||
$defaults = [
|
||||
$this->getKeyName(),
|
||||
$this->getCreatedAtColumn(),
|
||||
$this->getUpdatedAtColumn(),
|
||||
];
|
||||
|
||||
$attributes = Arr::except(
|
||||
$this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults
|
||||
);
|
||||
|
||||
return tap(new static, function ($instance) use ($attributes) {
|
||||
$instance->setRawAttributes($attributes);
|
||||
|
||||
$instance->setRelations($this->relations);
|
||||
|
||||
$instance->fireModelEvent('replicating', false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two models have the same ID and belong to the same table.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $model
|
||||
* @return bool
|
||||
*/
|
||||
public function is($model)
|
||||
{
|
||||
return ! is_null($model) &&
|
||||
$this->getKey() === $model->getKey() &&
|
||||
$this->getTable() === $model->getTable() &&
|
||||
$this->getConnectionName() === $model->getConnectionName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two models are not the same.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $model
|
||||
* @return bool
|
||||
*/
|
||||
public function isNot($model)
|
||||
{
|
||||
return ! $this->is($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection for the model.
|
||||
*
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return static::resolveConnection($this->getConnectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current connection name for the model.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getConnectionName()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection associated with the model.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnection($name)
|
||||
{
|
||||
$this->connection = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a connection instance.
|
||||
*
|
||||
* @param string|null $connection
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public static function resolveConnection($connection = null)
|
||||
{
|
||||
return static::$resolver->connection($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection resolver instance.
|
||||
*
|
||||
* @return \Illuminate\Database\ConnectionResolverInterface
|
||||
*/
|
||||
public static function getConnectionResolver()
|
||||
{
|
||||
return static::$resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection resolver instance.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
|
||||
* @return void
|
||||
*/
|
||||
public static function setConnectionResolver(Resolver $resolver)
|
||||
{
|
||||
static::$resolver = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the connection resolver for models.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unsetConnectionResolver()
|
||||
{
|
||||
static::$resolver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table associated with the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table associated with the model.
|
||||
*
|
||||
* @param string $table
|
||||
* @return $this
|
||||
*/
|
||||
public function setTable($table)
|
||||
{
|
||||
$this->table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyName()
|
||||
{
|
||||
return $this->primaryKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the primary key for the model.
|
||||
*
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function setKeyName($key)
|
||||
{
|
||||
$this->primaryKey = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table qualified key name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedKeyName()
|
||||
{
|
||||
return $this->qualifyColumn($this->getKeyName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-incrementing key type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyType()
|
||||
{
|
||||
return $this->keyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data type for the primary key.
|
||||
*
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
public function setKeyType($type)
|
||||
{
|
||||
$this->keyType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value indicating whether the IDs are incrementing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIncrementing()
|
||||
{
|
||||
return $this->incrementing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether IDs are incrementing.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setIncrementing($value)
|
||||
{
|
||||
$this->incrementing = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's primary key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->getAttribute($this->getKeyName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queueable identity for the entity.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getQueueableId()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queueable relationships for the entity.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueueableRelations()
|
||||
{
|
||||
$relations = [];
|
||||
|
||||
foreach ($this->getRelations() as $key => $relation) {
|
||||
if (! method_exists($this, $key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relations[] = $key;
|
||||
|
||||
if ($relation instanceof QueueableCollection) {
|
||||
foreach ($relation->getQueueableRelations() as $collectionValue) {
|
||||
$relations[] = $key.'.'.$collectionValue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($relation instanceof QueueableEntity) {
|
||||
foreach ($relation->getQueueableRelations() as $entityKey => $entityValue) {
|
||||
$relations[] = $key.'.'.$entityValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queueable connection for the entity.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQueueableConnection()
|
||||
{
|
||||
return $this->getConnectionName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's route key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRouteKey()
|
||||
{
|
||||
return $this->getAttribute($this->getRouteKeyName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRouteKeyName()
|
||||
{
|
||||
return $this->getKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model for a bound value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
public function resolveRouteBinding($value, $field = null)
|
||||
{
|
||||
return $this->where($field ?? $this->getRouteKeyName(), $value)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model for a bound value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
public function resolveSoftDeletableRouteBinding($value, $field = null)
|
||||
{
|
||||
return $this->where($field ?? $this->getRouteKeyName(), $value)->withTrashed()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the child model for a bound value.
|
||||
*
|
||||
* @param string $childType
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
public function resolveChildRouteBinding($childType, $value, $field)
|
||||
{
|
||||
return $this->resolveChildRouteBindingQuery($childType, $value, $field)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the child model for a bound value.
|
||||
*
|
||||
* @param string $childType
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
public function resolveSoftDeletableChildRouteBinding($childType, $value, $field)
|
||||
{
|
||||
return $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the child model query for a bound value.
|
||||
*
|
||||
* @param string $childType
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
protected function resolveChildRouteBindingQuery($childType, $value, $field)
|
||||
{
|
||||
$relationship = $this->{Str::plural(Str::camel($childType))}();
|
||||
|
||||
$field = $field ?: $relationship->getRelated()->getRouteKeyName();
|
||||
|
||||
if ($relationship instanceof HasManyThrough ||
|
||||
$relationship instanceof BelongsToMany) {
|
||||
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value);
|
||||
} else {
|
||||
return $relationship->where($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default foreign key name for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignKey()
|
||||
{
|
||||
return Str::snake(class_basename($this)).'_'.$this->getKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of models to return per page.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPerPage()
|
||||
{
|
||||
return $this->perPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of models to return per page.
|
||||
*
|
||||
* @param int $perPage
|
||||
* @return $this
|
||||
*/
|
||||
public function setPerPage($perPage)
|
||||
{
|
||||
$this->perPage = $perPage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if lazy loading is disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function preventsLazyLoading()
|
||||
{
|
||||
return static::$modelsShouldPreventLazyLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broadcast channel route definition that is associated with the given entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function broadcastChannelRoute()
|
||||
{
|
||||
return str_replace('\\', '.', get_class($this)).'.{'.Str::camel(class_basename($this)).'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broadcast channel name that is associated with the given entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function broadcastChannel()
|
||||
{
|
||||
return str_replace('\\', '.', get_class($this)).'.'.$this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically retrieve attributes on the model.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->getAttribute($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically set attributes on the model.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->setAttribute($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given attribute exists.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return ! is_null($this->getAttribute($offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given offset.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->getAttribute($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for a given offset.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->setAttribute($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the value for a given offset.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->attributes[$offset], $this->relations[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an attribute or relation exists on the model.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
return $this->offsetExists($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset an attribute on the model.
|
||||
*
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function __unset($key)
|
||||
{
|
||||
$this->offsetUnset($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic method calls into the model.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (in_array($method, ['increment', 'decrement'])) {
|
||||
return $this->$method(...$parameters);
|
||||
}
|
||||
|
||||
if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) {
|
||||
return $resolver($this);
|
||||
}
|
||||
|
||||
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic static method calls into the model.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic($method, $parameters)
|
||||
{
|
||||
return (new static)->$method(...$parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the model to its string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the object for serialization.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
$this->mergeAttributesFromClassCasts();
|
||||
|
||||
$this->classCastCache = [];
|
||||
|
||||
return array_keys(get_object_vars($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* When a model is being unserialized, check if it needs to be booted.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->bootIfNotBooted();
|
||||
|
||||
$this->initializeTraits();
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Database\RecordsNotFoundException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ModelNotFoundException extends RecordsNotFoundException
|
||||
{
|
||||
/**
|
||||
* Name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* The affected model IDs.
|
||||
*
|
||||
* @var int|array
|
||||
*/
|
||||
protected $ids;
|
||||
|
||||
/**
|
||||
* Set the affected Eloquent model and instance ids.
|
||||
*
|
||||
* @param string $model
|
||||
* @param int|array $ids
|
||||
* @return $this
|
||||
*/
|
||||
public function setModel($model, $ids = [])
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->ids = Arr::wrap($ids);
|
||||
|
||||
$this->message = "No query results for model [{$model}]";
|
||||
|
||||
if (count($this->ids) > 0) {
|
||||
$this->message .= ' '.implode(', ', $this->ids);
|
||||
} else {
|
||||
$this->message .= '.';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the affected Eloquent model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the affected Eloquent model IDs.
|
||||
*
|
||||
* @return int|array
|
||||
*/
|
||||
public function getIds()
|
||||
{
|
||||
return $this->ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use LogicException;
|
||||
|
||||
trait Prunable
|
||||
{
|
||||
/**
|
||||
* Prune all prunable models in the database.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return int
|
||||
*/
|
||||
public function pruneAll(int $chunkSize = 1000)
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
$this->prunable()
|
||||
->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($this))), function ($query) {
|
||||
$query->withTrashed();
|
||||
})->chunkById($chunkSize, function ($models) use (&$total) {
|
||||
$models->each->prune();
|
||||
|
||||
$total += $models->count();
|
||||
|
||||
event(new ModelsPruned(static::class, $total));
|
||||
});
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prunable model query.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function prunable()
|
||||
{
|
||||
throw new LogicException('Please implement the prunable method on your model.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune the model in the database.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$this->pruning();
|
||||
|
||||
return in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))
|
||||
? $this->forceDelete()
|
||||
: $this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the model for pruning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function pruning()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use Illuminate\Contracts\Queue\EntityNotFoundException;
|
||||
use Illuminate\Contracts\Queue\EntityResolver as EntityResolverContract;
|
||||
|
||||
class QueueEntityResolver implements EntityResolverContract
|
||||
{
|
||||
/**
|
||||
* Resolve the entity for the given ID.
|
||||
*
|
||||
* @param string $type
|
||||
* @param mixed $id
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Queue\EntityNotFoundException
|
||||
*/
|
||||
public function resolve($type, $id)
|
||||
{
|
||||
$instance = (new $type)->find($id);
|
||||
|
||||
if ($instance) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new EntityNotFoundException($type, $id);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class RelationNotFoundException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* The name of the affected Eloquent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* The name of the relation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $relation;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param object $model
|
||||
* @param string $relation
|
||||
* @return static
|
||||
*/
|
||||
public static function make($model, $relation)
|
||||
{
|
||||
$class = get_class($model);
|
||||
|
||||
$instance = new static("Call to undefined relationship [{$relation}] on model [{$class}].");
|
||||
|
||||
$instance->model = $class;
|
||||
$instance->relation = $relation;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesRelatedModels;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
|
||||
|
||||
class BelongsTo extends Relation
|
||||
{
|
||||
use ComparesRelatedModels,
|
||||
InteractsWithDictionary,
|
||||
SupportsDefaultModels;
|
||||
|
||||
/**
|
||||
* The child model instance of the relation.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $child;
|
||||
|
||||
/**
|
||||
* The foreign key of the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foreignKey;
|
||||
|
||||
/**
|
||||
* The associated key on the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ownerKey;
|
||||
|
||||
/**
|
||||
* The name of the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationName;
|
||||
|
||||
/**
|
||||
* Create a new belongs to relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $child
|
||||
* @param string $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $relationName
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName)
|
||||
{
|
||||
$this->ownerKey = $ownerKey;
|
||||
$this->relationName = $relationName;
|
||||
$this->foreignKey = $foreignKey;
|
||||
|
||||
// In the underlying base relationship class, this variable is referred to as
|
||||
// the "parent" since most relationships are not inversed. But, since this
|
||||
// one is we will create a "child" variable for much better readability.
|
||||
$this->child = $child;
|
||||
|
||||
parent::__construct($query, $child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
if (is_null($this->child->{$this->foreignKey})) {
|
||||
return $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
return $this->query->first() ?: $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base constraints on the relation query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addConstraints()
|
||||
{
|
||||
if (static::$constraints) {
|
||||
// For belongs to relationships, which are essentially the inverse of has one
|
||||
// or has many relationships, we need to actually query on the primary key
|
||||
// of the related models matching on the foreign key that's on a parent.
|
||||
$table = $this->related->getTable();
|
||||
|
||||
$this->query->where($table.'.'.$this->ownerKey, '=', $this->child->{$this->foreignKey});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
// We'll grab the primary key name of the related models since it could be set to
|
||||
// a non-standard name and not "id". We will then construct the constraint for
|
||||
// our eagerly loading query so it returns the proper models from execution.
|
||||
$key = $this->related->getTable().'.'.$this->ownerKey;
|
||||
|
||||
$whereIn = $this->whereInMethod($this->related, $this->ownerKey);
|
||||
|
||||
$this->query->{$whereIn}($key, $this->getEagerModelKeys($models));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather the keys from an array of related models.
|
||||
*
|
||||
* @param array $models
|
||||
* @return array
|
||||
*/
|
||||
protected function getEagerModelKeys(array $models)
|
||||
{
|
||||
$keys = [];
|
||||
|
||||
// First we need to gather all of the keys from the parent models so we know what
|
||||
// to query for via the eager loading query. We will add them to an array then
|
||||
// execute a "where in" statement to gather up all of those related records.
|
||||
foreach ($models as $model) {
|
||||
if (! is_null($value = $model->{$this->foreignKey})) {
|
||||
$keys[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
sort($keys);
|
||||
|
||||
return array_values(array_unique($keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->getDefaultFor($model));
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
$foreign = $this->foreignKey;
|
||||
|
||||
$owner = $this->ownerKey;
|
||||
|
||||
// First we will get to build a dictionary of the child models by their primary
|
||||
// key of the relationship, then we can easily match the children back onto
|
||||
// the parents using that dictionary and the primary key of the children.
|
||||
$dictionary = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$attribute = $this->getDictionaryKey($result->getAttribute($owner));
|
||||
|
||||
$dictionary[$attribute] = $result;
|
||||
}
|
||||
|
||||
// Once we have the dictionary constructed, we can loop through all the parents
|
||||
// and match back onto their children using these keys of the dictionary and
|
||||
// the primary key of the children to map them onto the correct instances.
|
||||
foreach ($models as $model) {
|
||||
$attribute = $this->getDictionaryKey($model->{$foreign});
|
||||
|
||||
if (isset($dictionary[$attribute])) {
|
||||
$model->setRelation($relation, $dictionary[$attribute]);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the model instance to the given parent.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|int|string $model
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function associate($model)
|
||||
{
|
||||
$ownerKey = $model instanceof Model ? $model->getAttribute($this->ownerKey) : $model;
|
||||
|
||||
$this->child->setAttribute($this->foreignKey, $ownerKey);
|
||||
|
||||
if ($model instanceof Model) {
|
||||
$this->child->setRelation($this->relationName, $model);
|
||||
} else {
|
||||
$this->child->unsetRelation($this->relationName);
|
||||
}
|
||||
|
||||
return $this->child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dissociate previously associated model from the given parent.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function dissociate()
|
||||
{
|
||||
$this->child->setAttribute($this->foreignKey, null);
|
||||
|
||||
return $this->child->setRelation($this->relationName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of "dissociate" method.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function disassociate()
|
||||
{
|
||||
return $this->dissociate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
if ($parentQuery->getQuery()->from == $query->getQuery()->from) {
|
||||
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
return $query->select($columns)->whereColumn(
|
||||
$this->getQualifiedForeignKeyName(), '=', $query->qualifyColumn($this->ownerKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query on the same table.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
$query->select($columns)->from(
|
||||
$query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()
|
||||
);
|
||||
|
||||
$query->getModel()->setTable($hash);
|
||||
|
||||
return $query->whereColumn(
|
||||
$hash.'.'.$this->ownerKey, '=', $this->getQualifiedForeignKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the related model has an auto-incrementing ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function relationHasIncrementingId()
|
||||
{
|
||||
return $this->related->getIncrementing() &&
|
||||
in_array($this->related->getKeyType(), ['int', 'integer']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected function newRelatedInstanceFor(Model $parent)
|
||||
{
|
||||
return $this->related->newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child of the relationship.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function getChild()
|
||||
{
|
||||
return $this->child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignKeyName()
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified foreign key of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedForeignKeyName()
|
||||
{
|
||||
return $this->child->qualifyColumn($this->foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key value of the child's foreign key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParentKey()
|
||||
{
|
||||
return $this->child->{$this->foreignKey};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated key of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOwnerKeyName()
|
||||
{
|
||||
return $this->ownerKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified associated key of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedOwnerKeyName()
|
||||
{
|
||||
return $this->related->qualifyColumn($this->ownerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's associated key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelatedKeyFrom(Model $model)
|
||||
{
|
||||
return $model->{$this->ownerKey};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationName()
|
||||
{
|
||||
return $this->relationName;
|
||||
}
|
||||
}
|
||||
+1412
@@ -0,0 +1,1412 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class BelongsToMany extends Relation
|
||||
{
|
||||
use InteractsWithDictionary, InteractsWithPivotTable;
|
||||
|
||||
/**
|
||||
* The intermediate table for the relation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* The foreign key of the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foreignPivotKey;
|
||||
|
||||
/**
|
||||
* The associated key of the relation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relatedPivotKey;
|
||||
|
||||
/**
|
||||
* The key name of the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $parentKey;
|
||||
|
||||
/**
|
||||
* The key name of the related model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relatedKey;
|
||||
|
||||
/**
|
||||
* The "name" of the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationName;
|
||||
|
||||
/**
|
||||
* The pivot table columns to retrieve.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pivotColumns = [];
|
||||
|
||||
/**
|
||||
* Any pivot table restrictions for where clauses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pivotWheres = [];
|
||||
|
||||
/**
|
||||
* Any pivot table restrictions for whereIn clauses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pivotWhereIns = [];
|
||||
|
||||
/**
|
||||
* Any pivot table restrictions for whereNull clauses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pivotWhereNulls = [];
|
||||
|
||||
/**
|
||||
* The default values for the pivot columns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pivotValues = [];
|
||||
|
||||
/**
|
||||
* Indicates if timestamps are available on the pivot table.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $withTimestamps = false;
|
||||
|
||||
/**
|
||||
* The custom pivot table column for the created_at timestamp.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pivotCreatedAt;
|
||||
|
||||
/**
|
||||
* The custom pivot table column for the updated_at timestamp.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pivotUpdatedAt;
|
||||
|
||||
/**
|
||||
* The class name of the custom pivot model to use for the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $using;
|
||||
|
||||
/**
|
||||
* The name of the accessor to use for the "pivot" relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $accessor = 'pivot';
|
||||
|
||||
/**
|
||||
* Create a new belongs to many relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $table
|
||||
* @param string $foreignPivotKey
|
||||
* @param string $relatedPivotKey
|
||||
* @param string $parentKey
|
||||
* @param string $relatedKey
|
||||
* @param string|null $relationName
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey, $relationName = null)
|
||||
{
|
||||
$this->parentKey = $parentKey;
|
||||
$this->relatedKey = $relatedKey;
|
||||
$this->relationName = $relationName;
|
||||
$this->relatedPivotKey = $relatedPivotKey;
|
||||
$this->foreignPivotKey = $foreignPivotKey;
|
||||
$this->table = $this->resolveTableName($table);
|
||||
|
||||
parent::__construct($query, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to resolve the intermediate table name from the given string.
|
||||
*
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveTableName($table)
|
||||
{
|
||||
if (! Str::contains($table, '\\') || ! class_exists($table)) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
$model = new $table;
|
||||
|
||||
if (! $model instanceof Model) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
if (in_array(AsPivot::class, class_uses_recursive($model))) {
|
||||
$this->using($table);
|
||||
}
|
||||
|
||||
return $model->getTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base constraints on the relation query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addConstraints()
|
||||
{
|
||||
$this->performJoin();
|
||||
|
||||
if (static::$constraints) {
|
||||
$this->addWhereConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the join clause for the relation query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|null $query
|
||||
* @return $this
|
||||
*/
|
||||
protected function performJoin($query = null)
|
||||
{
|
||||
$query = $query ?: $this->query;
|
||||
|
||||
// We need to join to the intermediate table on the related model's primary
|
||||
// key column with the intermediate table's foreign key for the related
|
||||
// model instance. Then we can set the "where" for the parent models.
|
||||
$query->join(
|
||||
$this->table,
|
||||
$this->getQualifiedRelatedKeyName(),
|
||||
'=',
|
||||
$this->getQualifiedRelatedPivotKeyName()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the where clause for the relation query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function addWhereConstraints()
|
||||
{
|
||||
$this->query->where(
|
||||
$this->getQualifiedForeignPivotKeyName(), '=', $this->parent->{$this->parentKey}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
$whereIn = $this->whereInMethod($this->parent, $this->parentKey);
|
||||
|
||||
$this->query->{$whereIn}(
|
||||
$this->getQualifiedForeignPivotKeyName(),
|
||||
$this->getKeys($models, $this->parentKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->related->newCollection());
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
$dictionary = $this->buildDictionary($results);
|
||||
|
||||
// Once we have an array dictionary of child objects we can easily match the
|
||||
// children back to their parent using the dictionary and the keys on the
|
||||
// the parent models. Then we will return the hydrated models back out.
|
||||
foreach ($models as $model) {
|
||||
$key = $this->getDictionaryKey($model->{$this->parentKey});
|
||||
|
||||
if (isset($dictionary[$key])) {
|
||||
$model->setRelation(
|
||||
$relation, $this->related->newCollection($dictionary[$key])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build model dictionary keyed by the relation's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @return array
|
||||
*/
|
||||
protected function buildDictionary(Collection $results)
|
||||
{
|
||||
// First we will build a dictionary of child models keyed by the foreign key
|
||||
// of the relation so that we will easily and quickly match them to their
|
||||
// parents without having a possibly slow inner loops for every models.
|
||||
$dictionary = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$value = $this->getDictionaryKey($result->{$this->accessor}->{$this->foreignPivotKey});
|
||||
|
||||
$dictionary[$value][] = $result;
|
||||
}
|
||||
|
||||
return $dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class being used for pivot models.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPivotClass()
|
||||
{
|
||||
return $this->using ?? Pivot::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the custom pivot model to use for the relationship.
|
||||
*
|
||||
* @param string $class
|
||||
* @return $this
|
||||
*/
|
||||
public function using($class)
|
||||
{
|
||||
$this->using = $class;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the custom pivot accessor to use for the relationship.
|
||||
*
|
||||
* @param string $accessor
|
||||
* @return $this
|
||||
*/
|
||||
public function as($accessor)
|
||||
{
|
||||
$this->accessor = $accessor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a where clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
|
||||
{
|
||||
$this->pivotWheres[] = func_get_args();
|
||||
|
||||
return $this->where($this->qualifyPivotColumn($column), $operator, $value, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "where between" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param array $values
|
||||
* @param string $boolean
|
||||
* @param bool $not
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivotBetween($column, array $values, $boolean = 'and', $not = false)
|
||||
{
|
||||
return $this->whereBetween($this->qualifyPivotColumn($column), $values, $boolean, $not);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "or where between" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param array $values
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivotBetween($column, array $values)
|
||||
{
|
||||
return $this->wherePivotBetween($column, $values, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "where pivot not between" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param array $values
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivotNotBetween($column, array $values, $boolean = 'and')
|
||||
{
|
||||
return $this->wherePivotBetween($column, $values, $boolean, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "or where not between" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param array $values
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivotNotBetween($column, array $values)
|
||||
{
|
||||
return $this->wherePivotBetween($column, $values, 'or', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "where in" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @param string $boolean
|
||||
* @param bool $not
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivotIn($column, $values, $boolean = 'and', $not = false)
|
||||
{
|
||||
$this->pivotWhereIns[] = func_get_args();
|
||||
|
||||
return $this->whereIn($this->qualifyPivotColumn($column), $values, $boolean, $not);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an "or where" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivot($column, $operator = null, $value = null)
|
||||
{
|
||||
return $this->wherePivot($column, $operator, $value, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a where clause for a pivot table column.
|
||||
*
|
||||
* In addition, new pivot records will receive this value.
|
||||
*
|
||||
* @param string|array $column
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function withPivotValue($column, $value = null)
|
||||
{
|
||||
if (is_array($column)) {
|
||||
foreach ($column as $name => $value) {
|
||||
$this->withPivotValue($name, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_null($value)) {
|
||||
throw new InvalidArgumentException('The provided value may not be null.');
|
||||
}
|
||||
|
||||
$this->pivotValues[] = compact('column', 'value');
|
||||
|
||||
return $this->wherePivot($column, '=', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an "or where in" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivotIn($column, $values)
|
||||
{
|
||||
return $this->wherePivotIn($column, $values, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "where not in" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivotNotIn($column, $values, $boolean = 'and')
|
||||
{
|
||||
return $this->wherePivotIn($column, $values, $boolean, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an "or where not in" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param mixed $values
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivotNotIn($column, $values)
|
||||
{
|
||||
return $this->wherePivotNotIn($column, $values, 'or');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "where null" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $boolean
|
||||
* @param bool $not
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivotNull($column, $boolean = 'and', $not = false)
|
||||
{
|
||||
$this->pivotWhereNulls[] = func_get_args();
|
||||
|
||||
return $this->whereNull($this->qualifyPivotColumn($column), $boolean, $not);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "where not null" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $boolean
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivotNotNull($column, $boolean = 'and')
|
||||
{
|
||||
return $this->wherePivotNull($column, $boolean, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "or where null" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param bool $not
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivotNull($column, $not = false)
|
||||
{
|
||||
return $this->wherePivotNull($column, 'or', $not);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "or where not null" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @return $this
|
||||
*/
|
||||
public function orWherePivotNotNull($column)
|
||||
{
|
||||
return $this->orWherePivotNull($column, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an "order by" clause for a pivot table column.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $direction
|
||||
* @return $this
|
||||
*/
|
||||
public function orderByPivot($column, $direction = 'asc')
|
||||
{
|
||||
return $this->orderBy($this->qualifyPivotColumn($column), $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key or return a new instance of the related model.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function findOrNew($id, $columns = ['*'])
|
||||
{
|
||||
if (is_null($instance = $this->find($id, $columns))) {
|
||||
$instance = $this->related->newInstance();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first related model record matching the attributes or instantiate it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function firstOrNew(array $attributes)
|
||||
{
|
||||
if (is_null($instance = $this->where($attributes)->first())) {
|
||||
$instance = $this->related->newInstance($attributes);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first related record matching the attributes or create it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $joining
|
||||
* @param bool $touch
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function firstOrCreate(array $attributes, array $joining = [], $touch = true)
|
||||
{
|
||||
if (is_null($instance = $this->where($attributes)->first())) {
|
||||
$instance = $this->create($attributes, $joining, $touch);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a related record matching the attributes, and fill it with values.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @param array $joining
|
||||
* @param bool $touch
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
|
||||
{
|
||||
if (is_null($instance = $this->where($attributes)->first())) {
|
||||
return $this->create($values, $joining, $touch);
|
||||
}
|
||||
|
||||
$instance->fill($values);
|
||||
|
||||
$instance->save(['touch' => false]);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
|
||||
*/
|
||||
public function find($id, $columns = ['*'])
|
||||
{
|
||||
if (! $id instanceof Model && (is_array($id) || $id instanceof Arrayable)) {
|
||||
return $this->findMany($id, $columns);
|
||||
}
|
||||
|
||||
return $this->where(
|
||||
$this->getRelated()->getQualifiedKeyName(), '=', $this->parseId($id)
|
||||
)->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple related models by their primary keys.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Support\Arrayable|array $ids
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function findMany($ids, $columns = ['*'])
|
||||
{
|
||||
$ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
|
||||
|
||||
if (empty($ids)) {
|
||||
return $this->getRelated()->newCollection();
|
||||
}
|
||||
|
||||
return $this->whereIn(
|
||||
$this->getRelated()->getQualifiedKeyName(), $this->parseIds($ids)
|
||||
)->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key or throw an exception.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function findOrFail($id, $columns = ['*'])
|
||||
{
|
||||
$result = $this->find($id, $columns);
|
||||
|
||||
$id = $id instanceof Arrayable ? $id->toArray() : $id;
|
||||
|
||||
if (is_array($id)) {
|
||||
if (count($result) === count(array_unique($id))) {
|
||||
return $result;
|
||||
}
|
||||
} elseif (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic where clause to the query, and return the first result.
|
||||
*
|
||||
* @param \Closure|string|array $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @param string $boolean
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
|
||||
{
|
||||
return $this->where($column, $operator, $value, $boolean)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return mixed
|
||||
*/
|
||||
public function first($columns = ['*'])
|
||||
{
|
||||
$results = $this->take(1)->get($columns);
|
||||
|
||||
return count($results) > 0 ? $results->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result or throw an exception.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function firstOrFail($columns = ['*'])
|
||||
{
|
||||
if (! is_null($model = $this->first($columns))) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return ! is_null($this->parent->{$this->parentKey})
|
||||
? $this->get()
|
||||
: $this->related->newCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query as a "select" statement.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function get($columns = ['*'])
|
||||
{
|
||||
// First we'll add the proper select columns onto the query so it is run with
|
||||
// the proper columns. Then, we will get the results and hydrate our pivot
|
||||
// models with the result of those columns as a separate model relation.
|
||||
$builder = $this->query->applyScopes();
|
||||
|
||||
$columns = $builder->getQuery()->columns ? [] : $columns;
|
||||
|
||||
$models = $builder->addSelect(
|
||||
$this->shouldSelect($columns)
|
||||
)->getModels();
|
||||
|
||||
$this->hydratePivotRelation($models);
|
||||
|
||||
// If we actually found models we will also eager load any relationships that
|
||||
// have been specified as needing to be eager loaded. This will solve the
|
||||
// n + 1 query problem for the developer and also increase performance.
|
||||
if (count($models) > 0) {
|
||||
$models = $builder->eagerLoadRelations($models);
|
||||
}
|
||||
|
||||
return $this->related->newCollection($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the select columns for the relation query.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return array
|
||||
*/
|
||||
protected function shouldSelect(array $columns = ['*'])
|
||||
{
|
||||
if ($columns == ['*']) {
|
||||
$columns = [$this->related->getTable().'.*'];
|
||||
}
|
||||
|
||||
return array_merge($columns, $this->aliasedPivotColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot columns for the relation.
|
||||
*
|
||||
* "pivot_" is prefixed ot each column for easy removal later.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function aliasedPivotColumns()
|
||||
{
|
||||
$defaults = [$this->foreignPivotKey, $this->relatedPivotKey];
|
||||
|
||||
return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
|
||||
return $this->qualifyPivotColumn($column).' as pivot_'.$column;
|
||||
})->unique()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a paginator for the "select" statement.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$this->query->addSelect($this->shouldSelect($columns));
|
||||
|
||||
return tap($this->query->paginate($perPage, $columns, $pageName, $page), function ($paginator) {
|
||||
$this->hydratePivotRelation($paginator->items());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query into a simple paginator.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
* @return \Illuminate\Contracts\Pagination\Paginator
|
||||
*/
|
||||
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$this->query->addSelect($this->shouldSelect($columns));
|
||||
|
||||
return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function ($paginator) {
|
||||
$this->hydratePivotRelation($paginator->items());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query into a cursor paginator.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $cursorName
|
||||
* @param string|null $cursor
|
||||
* @return \Illuminate\Contracts\Pagination\CursorPaginator
|
||||
*/
|
||||
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
|
||||
{
|
||||
$this->query->addSelect($this->shouldSelect($columns));
|
||||
|
||||
return tap($this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor), function ($paginator) {
|
||||
$this->hydratePivotRelation($paginator->items());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk the results of the query.
|
||||
*
|
||||
* @param int $count
|
||||
* @param callable $callback
|
||||
* @return bool
|
||||
*/
|
||||
public function chunk($count, callable $callback)
|
||||
{
|
||||
return $this->prepareQueryBuilder()->chunk($count, function ($results, $page) use ($callback) {
|
||||
$this->hydratePivotRelation($results->all());
|
||||
|
||||
return $callback($results, $page);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk the results of a query by comparing numeric IDs.
|
||||
*
|
||||
* @param int $count
|
||||
* @param callable $callback
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return bool
|
||||
*/
|
||||
public function chunkById($count, callable $callback, $column = null, $alias = null)
|
||||
{
|
||||
$this->prepareQueryBuilder();
|
||||
|
||||
$column = $column ?? $this->getRelated()->qualifyColumn(
|
||||
$this->getRelatedKeyName()
|
||||
);
|
||||
|
||||
$alias = $alias ?? $this->getRelatedKeyName();
|
||||
|
||||
return $this->query->chunkById($count, function ($results) use ($callback) {
|
||||
$this->hydratePivotRelation($results->all());
|
||||
|
||||
return $callback($results);
|
||||
}, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item while chunking.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @return bool
|
||||
*/
|
||||
public function each(callable $callback, $count = 1000)
|
||||
{
|
||||
return $this->chunk($count, function ($results) use ($callback) {
|
||||
foreach ($results as $key => $value) {
|
||||
if ($callback($value, $key) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunks of the given size.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazy($chunkSize = 1000)
|
||||
{
|
||||
return $this->prepareQueryBuilder()->lazy($chunkSize)->map(function ($model) {
|
||||
$this->hydratePivotRelation([$model]);
|
||||
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
|
||||
{
|
||||
$column = $column ?? $this->getRelated()->qualifyColumn(
|
||||
$this->getRelatedKeyName()
|
||||
);
|
||||
|
||||
$alias = $alias ?? $this->getRelatedKeyName();
|
||||
|
||||
return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias)->map(function ($model) {
|
||||
$this->hydratePivotRelation([$model]);
|
||||
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lazy collection for the given query.
|
||||
*
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function cursor()
|
||||
{
|
||||
return $this->prepareQueryBuilder()->cursor()->map(function ($model) {
|
||||
$this->hydratePivotRelation([$model]);
|
||||
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the query builder for query execution.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function prepareQueryBuilder()
|
||||
{
|
||||
return $this->query->addSelect($this->shouldSelect());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the pivot table relationship on the models.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
protected function hydratePivotRelation(array $models)
|
||||
{
|
||||
// To hydrate the pivot relationship, we will just gather the pivot attributes
|
||||
// and create a new Pivot model, which is basically a dynamic model that we
|
||||
// will set the attributes, table, and connections on it so it will work.
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($this->accessor, $this->newExistingPivot(
|
||||
$this->migratePivotAttributes($model)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot attributes from a model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return array
|
||||
*/
|
||||
protected function migratePivotAttributes(Model $model)
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($model->getAttributes() as $key => $value) {
|
||||
// To get the pivots attributes we will just take any of the attributes which
|
||||
// begin with "pivot_" and add those to this arrays, as well as unsetting
|
||||
// them from the parent's models since they exist in a different table.
|
||||
if (strpos($key, 'pivot_') === 0) {
|
||||
$values[substr($key, 6)] = $value;
|
||||
|
||||
unset($model->$key);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're touching the parent model, touch.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function touchIfTouching()
|
||||
{
|
||||
if ($this->touchingParent()) {
|
||||
$this->getParent()->touch();
|
||||
}
|
||||
|
||||
if ($this->getParent()->touches($this->relationName)) {
|
||||
$this->touch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should touch the parent on sync.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function touchingParent()
|
||||
{
|
||||
return $this->getRelated()->touches($this->guessInverseRelation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to guess the name of the inverse of the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function guessInverseRelation()
|
||||
{
|
||||
return Str::camel(Str::pluralStudly(class_basename($this->getParent())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch all of the related models for the relationship.
|
||||
*
|
||||
* E.g.: Touch all roles associated with this user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function touch()
|
||||
{
|
||||
$key = $this->getRelated()->getKeyName();
|
||||
|
||||
$columns = [
|
||||
$this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(),
|
||||
];
|
||||
|
||||
// If we actually have IDs for the relation, we will run the query to update all
|
||||
// the related model's timestamps, to make sure these all reflect the changes
|
||||
// to the parent models. This will help us keep any caching synced up here.
|
||||
if (count($ids = $this->allRelatedIds()) > 0) {
|
||||
$this->getRelated()->newQueryWithoutRelationships()->whereIn($key, $ids)->update($columns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the IDs for the related models.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function allRelatedIds()
|
||||
{
|
||||
return $this->newPivotQuery()->pluck($this->relatedPivotKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new model and attach it to the parent model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param array $pivotAttributes
|
||||
* @param bool $touch
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function save(Model $model, array $pivotAttributes = [], $touch = true)
|
||||
{
|
||||
$model->save(['touch' => false]);
|
||||
|
||||
$this->attach($model, $pivotAttributes, $touch);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an array of new models and attach them to the parent model.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array $models
|
||||
* @param array $pivotAttributes
|
||||
* @return array
|
||||
*/
|
||||
public function saveMany($models, array $pivotAttributes = [])
|
||||
{
|
||||
foreach ($models as $key => $model) {
|
||||
$this->save($model, (array) ($pivotAttributes[$key] ?? []), false);
|
||||
}
|
||||
|
||||
$this->touchIfTouching();
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the related model.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $joining
|
||||
* @param bool $touch
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function create(array $attributes = [], array $joining = [], $touch = true)
|
||||
{
|
||||
$instance = $this->related->newInstance($attributes);
|
||||
|
||||
// Once we save the related model, we need to attach it to the base model via
|
||||
// through intermediate table so we'll use the existing "attach" method to
|
||||
// accomplish this which will insert the record and any more attributes.
|
||||
$instance->save(['touch' => false]);
|
||||
|
||||
$this->attach($instance, $joining, $touch);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of new instances of the related models.
|
||||
*
|
||||
* @param iterable $records
|
||||
* @param array $joinings
|
||||
* @return array
|
||||
*/
|
||||
public function createMany(iterable $records, array $joinings = [])
|
||||
{
|
||||
$instances = [];
|
||||
|
||||
foreach ($records as $key => $record) {
|
||||
$instances[] = $this->create($record, (array) ($joinings[$key] ?? []), false);
|
||||
}
|
||||
|
||||
$this->touchIfTouching();
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
if ($parentQuery->getQuery()->from == $query->getQuery()->from) {
|
||||
return $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
$this->performJoin($query);
|
||||
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query on the same table.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
$query->select($columns);
|
||||
|
||||
$query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash());
|
||||
|
||||
$this->related->setTable($hash);
|
||||
|
||||
$this->performJoin($query);
|
||||
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key for comparing against the parent key in "has" query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExistenceCompareKey()
|
||||
{
|
||||
return $this->getQualifiedForeignPivotKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the pivot table has creation and update timestamps.
|
||||
*
|
||||
* @param mixed $createdAt
|
||||
* @param mixed $updatedAt
|
||||
* @return $this
|
||||
*/
|
||||
public function withTimestamps($createdAt = null, $updatedAt = null)
|
||||
{
|
||||
$this->withTimestamps = true;
|
||||
|
||||
$this->pivotCreatedAt = $createdAt;
|
||||
$this->pivotUpdatedAt = $updatedAt;
|
||||
|
||||
return $this->withPivot($this->createdAt(), $this->updatedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "created at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createdAt()
|
||||
{
|
||||
return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "updated at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function updatedAt()
|
||||
{
|
||||
return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key for the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignPivotKeyName()
|
||||
{
|
||||
return $this->foreignPivotKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified foreign key for the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedForeignPivotKeyName()
|
||||
{
|
||||
return $this->qualifyPivotColumn($this->foreignPivotKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "related key" for the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelatedPivotKeyName()
|
||||
{
|
||||
return $this->relatedPivotKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "related key" for the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedRelatedPivotKeyName()
|
||||
{
|
||||
return $this->qualifyPivotColumn($this->relatedPivotKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent key for the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getParentKeyName()
|
||||
{
|
||||
return $this->parentKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified parent key name for the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedParentKeyName()
|
||||
{
|
||||
return $this->parent->qualifyColumn($this->parentKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the related key for the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelatedKeyName()
|
||||
{
|
||||
return $this->relatedKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified related key name for the relation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedRelatedKeyName()
|
||||
{
|
||||
return $this->related->qualifyColumn($this->relatedKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the intermediate table for the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationship name for the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationName()
|
||||
{
|
||||
return $this->relationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the pivot accessor for this relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPivotAccessor()
|
||||
{
|
||||
return $this->accessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot columns for this relationship.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPivotColumns()
|
||||
{
|
||||
return $this->pivotColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify the given column name by the pivot table.
|
||||
*
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
public function qualifyPivotColumn($column)
|
||||
{
|
||||
return Str::contains($column, '.')
|
||||
? $column
|
||||
: $this->table.'.'.$column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait AsPivot
|
||||
{
|
||||
/**
|
||||
* The parent model of the relationship.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public $pivotParent;
|
||||
|
||||
/**
|
||||
* The name of the foreign key column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foreignKey;
|
||||
|
||||
/**
|
||||
* The name of the "other key" column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relatedKey;
|
||||
|
||||
/**
|
||||
* Create a new pivot model instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param array $attributes
|
||||
* @param string $table
|
||||
* @param bool $exists
|
||||
* @return static
|
||||
*/
|
||||
public static function fromAttributes(Model $parent, $attributes, $table, $exists = false)
|
||||
{
|
||||
$instance = new static;
|
||||
|
||||
$instance->timestamps = $instance->hasTimestampAttributes($attributes);
|
||||
|
||||
// The pivot model is a "dynamic" model since we will set the tables dynamically
|
||||
// for the instance. This allows it work for any intermediate tables for the
|
||||
// many to many relationship that are defined by this developer's classes.
|
||||
$instance->setConnection($parent->getConnectionName())
|
||||
->setTable($table)
|
||||
->forceFill($attributes)
|
||||
->syncOriginal();
|
||||
|
||||
// We store off the parent instance so we will access the timestamp column names
|
||||
// for the model, since the pivot model timestamps aren't easily configurable
|
||||
// from the developer's point of view. We can use the parents to get these.
|
||||
$instance->pivotParent = $parent;
|
||||
|
||||
$instance->exists = $exists;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pivot model from raw values returned from a query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param array $attributes
|
||||
* @param string $table
|
||||
* @param bool $exists
|
||||
* @return static
|
||||
*/
|
||||
public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false)
|
||||
{
|
||||
$instance = static::fromAttributes($parent, [], $table, $exists);
|
||||
|
||||
$instance->timestamps = $instance->hasTimestampAttributes($attributes);
|
||||
|
||||
$instance->setRawAttributes($attributes, $exists);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a select query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSelectQuery($query)
|
||||
{
|
||||
if (isset($this->attributes[$this->getKeyName()])) {
|
||||
return parent::setKeysForSelectQuery($query);
|
||||
}
|
||||
|
||||
$query->where($this->foreignKey, $this->getOriginal(
|
||||
$this->foreignKey, $this->getAttribute($this->foreignKey)
|
||||
));
|
||||
|
||||
return $query->where($this->relatedKey, $this->getOriginal(
|
||||
$this->relatedKey, $this->getAttribute($this->relatedKey)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a save update query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSaveQuery($query)
|
||||
{
|
||||
return $this->setKeysForSelectQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the pivot model record from the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if (isset($this->attributes[$this->getKeyName()])) {
|
||||
return (int) parent::delete();
|
||||
}
|
||||
|
||||
if ($this->fireModelEvent('deleting') === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->touchOwners();
|
||||
|
||||
return tap($this->getDeleteQuery()->delete(), function () {
|
||||
$this->exists = false;
|
||||
|
||||
$this->fireModelEvent('deleted', false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query builder for a delete operation on the pivot.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function getDeleteQuery()
|
||||
{
|
||||
return $this->newQueryWithoutRelationships()->where([
|
||||
$this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)),
|
||||
$this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table associated with the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
if (! isset($this->table)) {
|
||||
$this->setTable(str_replace(
|
||||
'\\', '', Str::snake(Str::singular(class_basename($this)))
|
||||
));
|
||||
}
|
||||
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key column name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignKey()
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "related key" column name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelatedKey()
|
||||
{
|
||||
return $this->relatedKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "related key" column name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOtherKey()
|
||||
{
|
||||
return $this->getRelatedKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the key names for the pivot model instance.
|
||||
*
|
||||
* @param string $foreignKey
|
||||
* @param string $relatedKey
|
||||
* @return $this
|
||||
*/
|
||||
public function setPivotKeys($foreignKey, $relatedKey)
|
||||
{
|
||||
$this->foreignKey = $foreignKey;
|
||||
|
||||
$this->relatedKey = $relatedKey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the pivot model or given attributes has timestamp attributes.
|
||||
*
|
||||
* @param array|null $attributes
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTimestampAttributes($attributes = null)
|
||||
{
|
||||
return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "created at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCreatedAtColumn()
|
||||
{
|
||||
return $this->pivotParent
|
||||
? $this->pivotParent->getCreatedAtColumn()
|
||||
: parent::getCreatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "updated at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdatedAtColumn()
|
||||
{
|
||||
return $this->pivotParent
|
||||
? $this->pivotParent->getUpdatedAtColumn()
|
||||
: parent::getUpdatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queueable identity for the entity.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getQueueableId()
|
||||
{
|
||||
if (isset($this->attributes[$this->getKeyName()])) {
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s:%s:%s:%s',
|
||||
$this->foreignKey, $this->getAttribute($this->foreignKey),
|
||||
$this->relatedKey, $this->getAttribute($this->relatedKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query to restore one or more models by their queueable IDs.
|
||||
*
|
||||
* @param int[]|string[]|string $ids
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newQueryForRestoration($ids)
|
||||
{
|
||||
if (is_array($ids)) {
|
||||
return $this->newQueryForCollectionRestoration($ids);
|
||||
}
|
||||
|
||||
if (! Str::contains($ids, ':')) {
|
||||
return parent::newQueryForRestoration($ids);
|
||||
}
|
||||
|
||||
$segments = explode(':', $ids);
|
||||
|
||||
return $this->newQueryWithoutScopes()
|
||||
->where($segments[0], $segments[1])
|
||||
->where($segments[2], $segments[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query to restore multiple models by their queueable IDs.
|
||||
*
|
||||
* @param int[]|string[] $ids
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function newQueryForCollectionRestoration(array $ids)
|
||||
{
|
||||
$ids = array_values($ids);
|
||||
|
||||
if (! Str::contains($ids[0], ':')) {
|
||||
return parent::newQueryForRestoration($ids);
|
||||
}
|
||||
|
||||
$query = $this->newQueryWithoutScopes();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$segments = explode(':', $id);
|
||||
|
||||
$query->orWhere(function ($query) use ($segments) {
|
||||
return $query->where($segments[0], $segments[1])
|
||||
->where($segments[2], $segments[3]);
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset all the loaded relations for the instance.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetRelations()
|
||||
{
|
||||
$this->pivotParent = null;
|
||||
$this->relations = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
trait CanBeOneOfMany
|
||||
{
|
||||
/**
|
||||
* Determines whether the relationship is one-of-many.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isOneOfMany = false;
|
||||
|
||||
/**
|
||||
* The name of the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationName;
|
||||
|
||||
/**
|
||||
* The one of many inner join subselect query builder instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder|null
|
||||
*/
|
||||
protected $oneOfManySubQuery;
|
||||
|
||||
/**
|
||||
* Add constraints for inner join subselect for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return void
|
||||
*/
|
||||
abstract public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null);
|
||||
|
||||
/**
|
||||
* Get the columns the determine the relationship groups.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
abstract public function getOneOfManySubQuerySelectColumns();
|
||||
|
||||
/**
|
||||
* Add join query constraints for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\JoinClause $join
|
||||
* @return void
|
||||
*/
|
||||
abstract public function addOneOfManyJoinSubQueryConstraints(JoinClause $join);
|
||||
|
||||
/**
|
||||
* Indicate that the relation is a single result of a larger one-to-many relationship.
|
||||
*
|
||||
* @param string|array|null $column
|
||||
* @param string|Closure|null $aggregate
|
||||
* @param string|null $relation
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null)
|
||||
{
|
||||
$this->isOneOfMany = true;
|
||||
|
||||
$this->relationName = $relation ?: $this->getDefaultOneOfManyJoinAlias(
|
||||
$this->guessRelationship()
|
||||
);
|
||||
|
||||
$keyName = $this->query->getModel()->getKeyName();
|
||||
|
||||
$columns = is_string($columns = $column) ? [
|
||||
$column => $aggregate,
|
||||
$keyName => $aggregate,
|
||||
] : $column;
|
||||
|
||||
if (! array_key_exists($keyName, $columns)) {
|
||||
$columns[$keyName] = 'MAX';
|
||||
}
|
||||
|
||||
if ($aggregate instanceof Closure) {
|
||||
$closure = $aggregate;
|
||||
}
|
||||
|
||||
foreach ($columns as $column => $aggregate) {
|
||||
if (! in_array(strtolower($aggregate), ['min', 'max'])) {
|
||||
throw new InvalidArgumentException("Invalid aggregate [{$aggregate}] used within ofMany relation. Available aggregates: MIN, MAX");
|
||||
}
|
||||
|
||||
$subQuery = $this->newOneOfManySubQuery(
|
||||
$this->getOneOfManySubQuerySelectColumns(),
|
||||
$column, $aggregate
|
||||
);
|
||||
|
||||
if (isset($previous)) {
|
||||
$this->addOneOfManyJoinSubQuery($subQuery, $previous['subQuery'], $previous['column']);
|
||||
} elseif (isset($closure)) {
|
||||
$closure($subQuery);
|
||||
}
|
||||
|
||||
if (! isset($previous)) {
|
||||
$this->oneOfManySubQuery = $subQuery;
|
||||
}
|
||||
|
||||
if (array_key_last($columns) == $column) {
|
||||
$this->addOneOfManyJoinSubQuery($this->query, $subQuery, $column);
|
||||
}
|
||||
|
||||
$previous = [
|
||||
'subQuery' => $subQuery,
|
||||
'column' => $column,
|
||||
];
|
||||
}
|
||||
|
||||
$this->addConstraints();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the relation is the latest single result of a larger one-to-many relationship.
|
||||
*
|
||||
* @param string|array|null $column
|
||||
* @param string|Closure|null $aggregate
|
||||
* @param string|null $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function latestOfMany($column = 'id', $relation = null)
|
||||
{
|
||||
return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) {
|
||||
return [$column => 'MAX'];
|
||||
})->all(), 'MAX', $relation ?: $this->guessRelationship());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the relation is the oldest single result of a larger one-to-many relationship.
|
||||
*
|
||||
* @param string|array|null $column
|
||||
* @param string|Closure|null $aggregate
|
||||
* @param string|null $relation
|
||||
* @return $this
|
||||
*/
|
||||
public function oldestOfMany($column = 'id', $relation = null)
|
||||
{
|
||||
return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) {
|
||||
return [$column => 'MIN'];
|
||||
})->all(), 'MIN', $relation ?: $this->guessRelationship());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default alias for the one of many inner join clause.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultOneOfManyJoinAlias($relation)
|
||||
{
|
||||
return $relation == $this->query->getModel()->getTable()
|
||||
? $relation.'_of_many'
|
||||
: $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query for the related model, grouping the query by the given column, often the foreign key of the relationship.
|
||||
*
|
||||
* @param string|array $groupBy
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function newOneOfManySubQuery($groupBy, $column = null, $aggregate = null)
|
||||
{
|
||||
$subQuery = $this->query->getModel()
|
||||
->newQuery();
|
||||
|
||||
foreach (Arr::wrap($groupBy) as $group) {
|
||||
$subQuery->groupBy($this->qualifyRelatedColumn($group));
|
||||
}
|
||||
|
||||
if (! is_null($column)) {
|
||||
$subQuery->selectRaw($aggregate.'('.$subQuery->getQuery()->grammar->wrap($column).') as '.$subQuery->getQuery()->grammar->wrap($column));
|
||||
}
|
||||
|
||||
$this->addOneOfManySubQueryConstraints($subQuery, $groupBy, $column, $aggregate);
|
||||
|
||||
return $subQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the join subquery to the given query on the given column and the relationship's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parent
|
||||
* @param \Illuminate\Database\Eloquent\Builder $subQuery
|
||||
* @param string $on
|
||||
* @return void
|
||||
*/
|
||||
protected function addOneOfManyJoinSubQuery(Builder $parent, Builder $subQuery, $on)
|
||||
{
|
||||
$parent->beforeQuery(function ($parent) use ($subQuery, $on) {
|
||||
$subQuery->applyBeforeQueryCallbacks();
|
||||
|
||||
$parent->joinSub($subQuery, $this->relationName, function ($join) use ($on) {
|
||||
$join->on($this->qualifySubSelectColumn($on), '=', $this->qualifyRelatedColumn($on));
|
||||
|
||||
$this->addOneOfManyJoinSubQueryConstraints($join, $on);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the relationship query joins to the given query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeOneOfManyJoinsTo(Builder $query)
|
||||
{
|
||||
$query->getQuery()->beforeQueryCallbacks = $this->query->getQuery()->beforeQueryCallbacks;
|
||||
|
||||
$query->applyBeforeQueryCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query builder that will contain the relationship constraints.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function getRelationQuery()
|
||||
{
|
||||
return $this->isOneOfMany()
|
||||
? $this->oneOfManySubQuery
|
||||
: $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the one of many inner join subselect builder instance.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder|void
|
||||
*/
|
||||
public function getOneOfManySubQuery()
|
||||
{
|
||||
return $this->oneOfManySubQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified column name for the one-of-many relationship using the subselect join query's alias.
|
||||
*
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
public function qualifySubSelectColumn($column)
|
||||
{
|
||||
return $this->getRelationName().'.'.last(explode('.', $column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Qualify related column using the related table name if it is not already qualified.
|
||||
*
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
protected function qualifyRelatedColumn($column)
|
||||
{
|
||||
return Str::contains($column, '.') ? $column : $this->query->getModel()->getTable().'.'.$column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the "hasOne" relationship's name via backtrace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function guessRelationship()
|
||||
{
|
||||
return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the relationship is a one-of-many relationship.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOneOfMany()
|
||||
{
|
||||
return $this->isOneOfMany;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationName()
|
||||
{
|
||||
return $this->relationName;
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\SupportsPartialRelations;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
trait ComparesRelatedModels
|
||||
{
|
||||
/**
|
||||
* Determine if the model is the related instance of the relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $model
|
||||
* @return bool
|
||||
*/
|
||||
public function is($model)
|
||||
{
|
||||
$match = ! is_null($model) &&
|
||||
$this->compareKeys($this->getParentKey(), $this->getRelatedKeyFrom($model)) &&
|
||||
$this->related->getTable() === $model->getTable() &&
|
||||
$this->related->getConnectionName() === $model->getConnectionName();
|
||||
|
||||
if ($match && $this instanceof SupportsPartialRelations && $this->isOneOfMany()) {
|
||||
return $this->query
|
||||
->whereKey($model->getKey())
|
||||
->exists();
|
||||
}
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is not the related instance of the relationship.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model|null $model
|
||||
* @return bool
|
||||
*/
|
||||
public function isNot($model)
|
||||
{
|
||||
return ! $this->is($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the parent model's key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function getParentKey();
|
||||
|
||||
/**
|
||||
* Get the value of the model's related key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getRelatedKeyFrom(Model $model);
|
||||
|
||||
/**
|
||||
* Compare the parent key with the related key.
|
||||
*
|
||||
* @param mixed $parentKey
|
||||
* @param mixed $relatedKey
|
||||
* @return bool
|
||||
*/
|
||||
protected function compareKeys($parentKey, $relatedKey)
|
||||
{
|
||||
if (empty($parentKey) || empty($relatedKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_int($parentKey) || is_int($relatedKey)) {
|
||||
return (int) $parentKey === (int) $relatedKey;
|
||||
}
|
||||
|
||||
return $parentKey === $relatedKey;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Doctrine\Instantiator\Exception\InvalidArgumentException;
|
||||
|
||||
trait InteractsWithDictionary
|
||||
{
|
||||
/**
|
||||
* Get a dictionary key attribute - casting it to a string if necessary.
|
||||
*
|
||||
* @param mixed $attribute
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Doctrine\Instantiator\Exception\InvalidArgumentException
|
||||
*/
|
||||
protected function getDictionaryKey($attribute)
|
||||
{
|
||||
if (is_object($attribute)) {
|
||||
if (method_exists($attribute, '__toString')) {
|
||||
return $attribute->__toString();
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Model attribute value is an object but does not have a __toString method.');
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
}
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
|
||||
trait InteractsWithPivotTable
|
||||
{
|
||||
/**
|
||||
* Toggles a model (or models) from the parent.
|
||||
*
|
||||
* Each existing model is detached, and non existing ones are attached.
|
||||
*
|
||||
* @param mixed $ids
|
||||
* @param bool $touch
|
||||
* @return array
|
||||
*/
|
||||
public function toggle($ids, $touch = true)
|
||||
{
|
||||
$changes = [
|
||||
'attached' => [], 'detached' => [],
|
||||
];
|
||||
|
||||
$records = $this->formatRecordsList($this->parseIds($ids));
|
||||
|
||||
// Next, we will determine which IDs should get removed from the join table by
|
||||
// checking which of the given ID/records is in the list of current records
|
||||
// and removing all of those rows from this "intermediate" joining table.
|
||||
$detach = array_values(array_intersect(
|
||||
$this->newPivotQuery()->pluck($this->relatedPivotKey)->all(),
|
||||
array_keys($records)
|
||||
));
|
||||
|
||||
if (count($detach) > 0) {
|
||||
$this->detach($detach, false);
|
||||
|
||||
$changes['detached'] = $this->castKeys($detach);
|
||||
}
|
||||
|
||||
// Finally, for all of the records which were not "detached", we'll attach the
|
||||
// records into the intermediate table. Then, we will add those attaches to
|
||||
// this change list and get ready to return these results to the callers.
|
||||
$attach = array_diff_key($records, array_flip($detach));
|
||||
|
||||
if (count($attach) > 0) {
|
||||
$this->attach($attach, [], false);
|
||||
|
||||
$changes['attached'] = array_keys($attach);
|
||||
}
|
||||
|
||||
// Once we have finished attaching or detaching the records, we will see if we
|
||||
// have done any attaching or detaching, and if we have we will touch these
|
||||
// relationships if they are configured to touch on any database updates.
|
||||
if ($touch && (count($changes['attached']) ||
|
||||
count($changes['detached']))) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the intermediate tables with a list of IDs without detaching.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
|
||||
* @return array
|
||||
*/
|
||||
public function syncWithoutDetaching($ids)
|
||||
{
|
||||
return $this->sync($ids, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the intermediate tables with a list of IDs or collection of models.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
|
||||
* @param bool $detaching
|
||||
* @return array
|
||||
*/
|
||||
public function sync($ids, $detaching = true)
|
||||
{
|
||||
$changes = [
|
||||
'attached' => [], 'detached' => [], 'updated' => [],
|
||||
];
|
||||
|
||||
// First we need to attach any of the associated models that are not currently
|
||||
// in this joining table. We'll spin through the given IDs, checking to see
|
||||
// if they exist in the array of current ones, and if not we will insert.
|
||||
$current = $this->getCurrentlyAttachedPivots()
|
||||
->pluck($this->relatedPivotKey)->all();
|
||||
|
||||
$detach = array_diff($current, array_keys(
|
||||
$records = $this->formatRecordsList($this->parseIds($ids))
|
||||
));
|
||||
|
||||
// Next, we will take the differences of the currents and given IDs and detach
|
||||
// all of the entities that exist in the "current" array but are not in the
|
||||
// array of the new IDs given to the method which will complete the sync.
|
||||
if ($detaching && count($detach) > 0) {
|
||||
$this->detach($detach);
|
||||
|
||||
$changes['detached'] = $this->castKeys($detach);
|
||||
}
|
||||
|
||||
// Now we are finally ready to attach the new records. Note that we'll disable
|
||||
// touching until after the entire operation is complete so we don't fire a
|
||||
// ton of touch operations until we are totally done syncing the records.
|
||||
$changes = array_merge(
|
||||
$changes, $this->attachNew($records, $current, false)
|
||||
);
|
||||
|
||||
// Once we have finished attaching or detaching the records, we will see if we
|
||||
// have done any attaching or detaching, and if we have we will touch these
|
||||
// relationships if they are configured to touch on any database updates.
|
||||
if (count($changes['attached']) ||
|
||||
count($changes['updated']) ||
|
||||
count($changes['detached'])) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the intermediate tables with a list of IDs or collection of models with the given pivot values.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
|
||||
* @param array $values
|
||||
* @param bool $detaching
|
||||
* @return array
|
||||
*/
|
||||
public function syncWithPivotValues($ids, array $values, bool $detaching = true)
|
||||
{
|
||||
return $this->sync(collect($this->parseIds($ids))->mapWithKeys(function ($id) use ($values) {
|
||||
return [$id => $values];
|
||||
}), $detaching);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the sync / toggle record list so that it is keyed by ID.
|
||||
*
|
||||
* @param array $records
|
||||
* @return array
|
||||
*/
|
||||
protected function formatRecordsList(array $records)
|
||||
{
|
||||
return collect($records)->mapWithKeys(function ($attributes, $id) {
|
||||
if (! is_array($attributes)) {
|
||||
[$id, $attributes] = [$attributes, []];
|
||||
}
|
||||
|
||||
return [$id => $attributes];
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach all of the records that aren't in the given current records.
|
||||
*
|
||||
* @param array $records
|
||||
* @param array $current
|
||||
* @param bool $touch
|
||||
* @return array
|
||||
*/
|
||||
protected function attachNew(array $records, array $current, $touch = true)
|
||||
{
|
||||
$changes = ['attached' => [], 'updated' => []];
|
||||
|
||||
foreach ($records as $id => $attributes) {
|
||||
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
|
||||
// record, otherwise, we will just update this existing record on this joining
|
||||
// table, so that the developers will easily update these records pain free.
|
||||
if (! in_array($id, $current)) {
|
||||
$this->attach($id, $attributes, $touch);
|
||||
|
||||
$changes['attached'][] = $this->castKey($id);
|
||||
}
|
||||
|
||||
// Now we'll try to update an existing pivot record with the attributes that were
|
||||
// given to the method. If the model is actually updated we will add it to the
|
||||
// list of updated pivot records so we return them back out to the consumer.
|
||||
elseif (count($attributes) > 0 &&
|
||||
$this->updateExistingPivot($id, $attributes, $touch)) {
|
||||
$changes['updated'][] = $this->castKey($id);
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing pivot record on the table.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $attributes
|
||||
* @param bool $touch
|
||||
* @return int
|
||||
*/
|
||||
public function updateExistingPivot($id, array $attributes, $touch = true)
|
||||
{
|
||||
if ($this->using &&
|
||||
empty($this->pivotWheres) &&
|
||||
empty($this->pivotWhereIns) &&
|
||||
empty($this->pivotWhereNulls)) {
|
||||
return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch);
|
||||
}
|
||||
|
||||
if (in_array($this->updatedAt(), $this->pivotColumns)) {
|
||||
$attributes = $this->addTimestampsToAttachment($attributes, true);
|
||||
}
|
||||
|
||||
$updated = $this->newPivotStatementForId($this->parseId($id))->update(
|
||||
$this->castAttributes($attributes)
|
||||
);
|
||||
|
||||
if ($touch) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing pivot record on the table via a custom class.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $attributes
|
||||
* @param bool $touch
|
||||
* @return int
|
||||
*/
|
||||
protected function updateExistingPivotUsingCustomClass($id, array $attributes, $touch)
|
||||
{
|
||||
$pivot = $this->getCurrentlyAttachedPivots()
|
||||
->where($this->foreignPivotKey, $this->parent->{$this->parentKey})
|
||||
->where($this->relatedPivotKey, $this->parseId($id))
|
||||
->first();
|
||||
|
||||
$updated = $pivot ? $pivot->fill($attributes)->isDirty() : false;
|
||||
|
||||
if ($updated) {
|
||||
$pivot->save();
|
||||
}
|
||||
|
||||
if ($touch) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
|
||||
return (int) $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a model to the parent.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $attributes
|
||||
* @param bool $touch
|
||||
* @return void
|
||||
*/
|
||||
public function attach($id, array $attributes = [], $touch = true)
|
||||
{
|
||||
if ($this->using) {
|
||||
$this->attachUsingCustomClass($id, $attributes);
|
||||
} else {
|
||||
// Here we will insert the attachment records into the pivot table. Once we have
|
||||
// inserted the records, we will touch the relationships if necessary and the
|
||||
// function will return. We can parse the IDs before inserting the records.
|
||||
$this->newPivotStatement()->insert($this->formatAttachRecords(
|
||||
$this->parseIds($id), $attributes
|
||||
));
|
||||
}
|
||||
|
||||
if ($touch) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a model to the parent using a custom class.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function attachUsingCustomClass($id, array $attributes)
|
||||
{
|
||||
$records = $this->formatAttachRecords(
|
||||
$this->parseIds($id), $attributes
|
||||
);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$this->newPivot($record, false)->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of records to insert into the pivot table.
|
||||
*
|
||||
* @param array $ids
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected function formatAttachRecords($ids, array $attributes)
|
||||
{
|
||||
$records = [];
|
||||
|
||||
$hasTimestamps = ($this->hasPivotColumn($this->createdAt()) ||
|
||||
$this->hasPivotColumn($this->updatedAt()));
|
||||
|
||||
// To create the attachment records, we will simply spin through the IDs given
|
||||
// and create a new record to insert for each ID. Each ID may actually be a
|
||||
// key in the array, with extra attributes to be placed in other columns.
|
||||
foreach ($ids as $key => $value) {
|
||||
$records[] = $this->formatAttachRecord(
|
||||
$key, $value, $attributes, $hasTimestamps
|
||||
);
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a full attachment record payload.
|
||||
*
|
||||
* @param int $key
|
||||
* @param mixed $value
|
||||
* @param array $attributes
|
||||
* @param bool $hasTimestamps
|
||||
* @return array
|
||||
*/
|
||||
protected function formatAttachRecord($key, $value, $attributes, $hasTimestamps)
|
||||
{
|
||||
[$id, $attributes] = $this->extractAttachIdAndAttributes($key, $value, $attributes);
|
||||
|
||||
return array_merge(
|
||||
$this->baseAttachRecord($id, $hasTimestamps), $this->castAttributes($attributes)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attach record ID and extra attributes.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected function extractAttachIdAndAttributes($key, $value, array $attributes)
|
||||
{
|
||||
return is_array($value)
|
||||
? [$key, array_merge($value, $attributes)]
|
||||
: [$value, $attributes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pivot attachment record.
|
||||
*
|
||||
* @param int $id
|
||||
* @param bool $timed
|
||||
* @return array
|
||||
*/
|
||||
protected function baseAttachRecord($id, $timed)
|
||||
{
|
||||
$record[$this->relatedPivotKey] = $id;
|
||||
|
||||
$record[$this->foreignPivotKey] = $this->parent->{$this->parentKey};
|
||||
|
||||
// If the record needs to have creation and update timestamps, we will make
|
||||
// them by calling the parent model's "freshTimestamp" method which will
|
||||
// provide us with a fresh timestamp in this model's preferred format.
|
||||
if ($timed) {
|
||||
$record = $this->addTimestampsToAttachment($record);
|
||||
}
|
||||
|
||||
foreach ($this->pivotValues as $value) {
|
||||
$record[$value['column']] = $value['value'];
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation and update timestamps on an attach record.
|
||||
*
|
||||
* @param array $record
|
||||
* @param bool $exists
|
||||
* @return array
|
||||
*/
|
||||
protected function addTimestampsToAttachment(array $record, $exists = false)
|
||||
{
|
||||
$fresh = $this->parent->freshTimestamp();
|
||||
|
||||
if ($this->using) {
|
||||
$pivotModel = new $this->using;
|
||||
|
||||
$fresh = $fresh->format($pivotModel->getDateFormat());
|
||||
}
|
||||
|
||||
if (! $exists && $this->hasPivotColumn($this->createdAt())) {
|
||||
$record[$this->createdAt()] = $fresh;
|
||||
}
|
||||
|
||||
if ($this->hasPivotColumn($this->updatedAt())) {
|
||||
$record[$this->updatedAt()] = $fresh;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given column is defined as a pivot column.
|
||||
*
|
||||
* @param string $column
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPivotColumn($column)
|
||||
{
|
||||
return in_array($column, $this->pivotColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach models from the relationship.
|
||||
*
|
||||
* @param mixed $ids
|
||||
* @param bool $touch
|
||||
* @return int
|
||||
*/
|
||||
public function detach($ids = null, $touch = true)
|
||||
{
|
||||
if ($this->using &&
|
||||
! empty($ids) &&
|
||||
empty($this->pivotWheres) &&
|
||||
empty($this->pivotWhereIns) &&
|
||||
empty($this->pivotWhereNulls)) {
|
||||
$results = $this->detachUsingCustomClass($ids);
|
||||
} else {
|
||||
$query = $this->newPivotQuery();
|
||||
|
||||
// If associated IDs were passed to the method we will only delete those
|
||||
// associations, otherwise all of the association ties will be broken.
|
||||
// We'll return the numbers of affected rows when we do the deletes.
|
||||
if (! is_null($ids)) {
|
||||
$ids = $this->parseIds($ids);
|
||||
|
||||
if (empty($ids)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query->whereIn($this->getQualifiedRelatedPivotKeyName(), (array) $ids);
|
||||
}
|
||||
|
||||
// Once we have all of the conditions set on the statement, we are ready
|
||||
// to run the delete on the pivot table. Then, if the touch parameter
|
||||
// is true, we will go ahead and touch all related models to sync.
|
||||
$results = $query->delete();
|
||||
}
|
||||
|
||||
if ($touch) {
|
||||
$this->touchIfTouching();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach models from the relationship using a custom class.
|
||||
*
|
||||
* @param mixed $ids
|
||||
* @return int
|
||||
*/
|
||||
protected function detachUsingCustomClass($ids)
|
||||
{
|
||||
$results = 0;
|
||||
|
||||
foreach ($this->parseIds($ids) as $id) {
|
||||
$results += $this->newPivot([
|
||||
$this->foreignPivotKey => $this->parent->{$this->parentKey},
|
||||
$this->relatedPivotKey => $id,
|
||||
], true)->delete();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot models that are currently attached.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getCurrentlyAttachedPivots()
|
||||
{
|
||||
return $this->newPivotQuery()->get()->map(function ($record) {
|
||||
$class = $this->using ?: Pivot::class;
|
||||
|
||||
$pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true);
|
||||
|
||||
return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pivot model instance.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param bool $exists
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Pivot
|
||||
*/
|
||||
public function newPivot(array $attributes = [], $exists = false)
|
||||
{
|
||||
$pivot = $this->related->newPivot(
|
||||
$this->parent, $attributes, $this->table, $exists, $this->using
|
||||
);
|
||||
|
||||
return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new existing pivot model instance.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Pivot
|
||||
*/
|
||||
public function newExistingPivot(array $attributes = [])
|
||||
{
|
||||
return $this->newPivot($attributes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new plain query builder for the pivot table.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function newPivotStatement()
|
||||
{
|
||||
return $this->query->getQuery()->newQuery()->from($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new pivot statement for a given "other" ID.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function newPivotStatementForId($id)
|
||||
{
|
||||
return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new query builder for the pivot table.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function newPivotQuery()
|
||||
{
|
||||
$query = $this->newPivotStatement();
|
||||
|
||||
foreach ($this->pivotWheres as $arguments) {
|
||||
$query->where(...$arguments);
|
||||
}
|
||||
|
||||
foreach ($this->pivotWhereIns as $arguments) {
|
||||
$query->whereIn(...$arguments);
|
||||
}
|
||||
|
||||
foreach ($this->pivotWhereNulls as $arguments) {
|
||||
$query->whereNull(...$arguments);
|
||||
}
|
||||
|
||||
return $query->where($this->getQualifiedForeignPivotKeyName(), $this->parent->{$this->parentKey});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the columns on the pivot table to retrieve.
|
||||
*
|
||||
* @param array|mixed $columns
|
||||
* @return $this
|
||||
*/
|
||||
public function withPivot($columns)
|
||||
{
|
||||
$this->pivotColumns = array_merge(
|
||||
$this->pivotColumns, is_array($columns) ? $columns : func_get_args()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the IDs from the given mixed value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function parseIds($value)
|
||||
{
|
||||
if ($value instanceof Model) {
|
||||
return [$value->{$this->relatedKey}];
|
||||
}
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
return $value->pluck($this->relatedKey)->all();
|
||||
}
|
||||
|
||||
if ($value instanceof BaseCollection) {
|
||||
return $value->toArray();
|
||||
}
|
||||
|
||||
return (array) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID from the given mixed value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function parseId($value)
|
||||
{
|
||||
return $value instanceof Model ? $value->{$this->relatedKey} : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the given keys to integers if they are numeric and string otherwise.
|
||||
*
|
||||
* @param array $keys
|
||||
* @return array
|
||||
*/
|
||||
protected function castKeys(array $keys)
|
||||
{
|
||||
return array_map(function ($v) {
|
||||
return $this->castKey($v);
|
||||
}, $keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the given key to convert to primary key type.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return mixed
|
||||
*/
|
||||
protected function castKey($key)
|
||||
{
|
||||
return $this->getTypeSwapValue(
|
||||
$this->related->getKeyType(),
|
||||
$key
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the given pivot attributes.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected function castAttributes($attributes)
|
||||
{
|
||||
return $this->using
|
||||
? $this->newPivot()->fill($attributes)->getAttributes()
|
||||
: $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given value to a given type value.
|
||||
*
|
||||
* @param string $type
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getTypeSwapValue($type, $value)
|
||||
{
|
||||
switch (strtolower($type)) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return (int) $value;
|
||||
case 'real':
|
||||
case 'float':
|
||||
case 'double':
|
||||
return (float) $value;
|
||||
case 'string':
|
||||
return (string) $value;
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
trait SupportsDefaultModels
|
||||
{
|
||||
/**
|
||||
* Indicates if a default model instance should be used.
|
||||
*
|
||||
* Alternatively, may be a Closure or array.
|
||||
*
|
||||
* @var \Closure|array|bool
|
||||
*/
|
||||
protected $withDefault;
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
abstract protected function newRelatedInstanceFor(Model $parent);
|
||||
|
||||
/**
|
||||
* Return a new model instance in case the relationship does not exist.
|
||||
*
|
||||
* @param \Closure|array|bool $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function withDefault($callback = true)
|
||||
{
|
||||
$this->withDefault = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value for this relation.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
protected function getDefaultFor(Model $parent)
|
||||
{
|
||||
if (! $this->withDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = $this->newRelatedInstanceFor($parent);
|
||||
|
||||
if (is_callable($this->withDefault)) {
|
||||
return call_user_func($this->withDefault, $instance, $parent) ?: $instance;
|
||||
}
|
||||
|
||||
if (is_array($this->withDefault)) {
|
||||
$instance->forceFill($this->withDefault);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class HasMany extends HasOneOrMany
|
||||
{
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return ! is_null($this->getParentKey())
|
||||
? $this->query->get()
|
||||
: $this->related->newCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->related->newCollection());
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $this->matchMany($models, $results, $relation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class HasManyThrough extends Relation
|
||||
{
|
||||
use InteractsWithDictionary;
|
||||
|
||||
/**
|
||||
* The "through" parent model instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $throughParent;
|
||||
|
||||
/**
|
||||
* The far parent model instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $farParent;
|
||||
|
||||
/**
|
||||
* The near key on the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $firstKey;
|
||||
|
||||
/**
|
||||
* The far key on the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $secondKey;
|
||||
|
||||
/**
|
||||
* The local key on the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $localKey;
|
||||
|
||||
/**
|
||||
* The local key on the intermediary model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $secondLocalKey;
|
||||
|
||||
/**
|
||||
* Create a new has many through relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $farParent
|
||||
* @param \Illuminate\Database\Eloquent\Model $throughParent
|
||||
* @param string $firstKey
|
||||
* @param string $secondKey
|
||||
* @param string $localKey
|
||||
* @param string $secondLocalKey
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
|
||||
{
|
||||
$this->localKey = $localKey;
|
||||
$this->firstKey = $firstKey;
|
||||
$this->secondKey = $secondKey;
|
||||
$this->farParent = $farParent;
|
||||
$this->throughParent = $throughParent;
|
||||
$this->secondLocalKey = $secondLocalKey;
|
||||
|
||||
parent::__construct($query, $throughParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base constraints on the relation query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addConstraints()
|
||||
{
|
||||
$localValue = $this->farParent[$this->localKey];
|
||||
|
||||
$this->performJoin();
|
||||
|
||||
if (static::$constraints) {
|
||||
$this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the join clause on the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder|null $query
|
||||
* @return void
|
||||
*/
|
||||
protected function performJoin(Builder $query = null)
|
||||
{
|
||||
$query = $query ?: $this->query;
|
||||
|
||||
$farKey = $this->getQualifiedFarKeyName();
|
||||
|
||||
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey);
|
||||
|
||||
if ($this->throughParentSoftDeletes()) {
|
||||
$query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) {
|
||||
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified parent key name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedParentKeyName()
|
||||
{
|
||||
return $this->parent->qualifyColumn($this->secondLocalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether "through" parent of the relation uses Soft Deletes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function throughParentSoftDeletes()
|
||||
{
|
||||
return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that trashed "through" parents should be included in the query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withTrashedParents()
|
||||
{
|
||||
$this->query->withoutGlobalScope('SoftDeletableHasManyThrough');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
$whereIn = $this->whereInMethod($this->farParent, $this->localKey);
|
||||
|
||||
$this->query->{$whereIn}(
|
||||
$this->getQualifiedFirstKeyName(), $this->getKeys($models, $this->localKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->related->newCollection());
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
$dictionary = $this->buildDictionary($results);
|
||||
|
||||
// Once we have the dictionary we can simply spin through the parent models to
|
||||
// link them up with their children using the keyed dictionary to make the
|
||||
// matching very convenient and easy work. Then we'll just return them.
|
||||
foreach ($models as $model) {
|
||||
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
|
||||
$model->setRelation(
|
||||
$relation, $this->related->newCollection($dictionary[$key])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build model dictionary keyed by the relation's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @return array
|
||||
*/
|
||||
protected function buildDictionary(Collection $results)
|
||||
{
|
||||
$dictionary = [];
|
||||
|
||||
// First we will create a dictionary of models keyed by the foreign key of the
|
||||
// relationship as this will allow us to quickly access all of the related
|
||||
// models without having to do nested looping which will be quite slow.
|
||||
foreach ($results as $result) {
|
||||
$dictionary[$result->laravel_through_key][] = $result;
|
||||
}
|
||||
|
||||
return $dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first related model record matching the attributes or instantiate it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function firstOrNew(array $attributes)
|
||||
{
|
||||
if (is_null($instance = $this->where($attributes)->first())) {
|
||||
$instance = $this->related->newInstance($attributes);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a related record matching the attributes, and fill it with values.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function updateOrCreate(array $attributes, array $values = [])
|
||||
{
|
||||
$instance = $this->firstOrNew($attributes);
|
||||
|
||||
$instance->fill($values)->save();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a basic where clause to the query, and return the first result.
|
||||
*
|
||||
* @param \Closure|string|array $column
|
||||
* @param mixed $operator
|
||||
* @param mixed $value
|
||||
* @param string $boolean
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*/
|
||||
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
|
||||
{
|
||||
return $this->where($column, $operator, $value, $boolean)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first related model.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return mixed
|
||||
*/
|
||||
public function first($columns = ['*'])
|
||||
{
|
||||
$results = $this->take(1)->get($columns);
|
||||
|
||||
return count($results) > 0 ? $results->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result or throw an exception.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|static
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function firstOrFail($columns = ['*'])
|
||||
{
|
||||
if (! is_null($model = $this->first($columns))) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
|
||||
*/
|
||||
public function find($id, $columns = ['*'])
|
||||
{
|
||||
if (is_array($id) || $id instanceof Arrayable) {
|
||||
return $this->findMany($id, $columns);
|
||||
}
|
||||
|
||||
return $this->where(
|
||||
$this->getRelated()->getQualifiedKeyName(), '=', $id
|
||||
)->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple related models by their primary keys.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Support\Arrayable|array $ids
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function findMany($ids, $columns = ['*'])
|
||||
{
|
||||
$ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
|
||||
|
||||
if (empty($ids)) {
|
||||
return $this->getRelated()->newCollection();
|
||||
}
|
||||
|
||||
return $this->whereIn(
|
||||
$this->getRelated()->getQualifiedKeyName(), $ids
|
||||
)->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a related model by its primary key or throw an exception.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function findOrFail($id, $columns = ['*'])
|
||||
{
|
||||
$result = $this->find($id, $columns);
|
||||
|
||||
$id = $id instanceof Arrayable ? $id->toArray() : $id;
|
||||
|
||||
if (is_array($id)) {
|
||||
if (count($result) === count(array_unique($id))) {
|
||||
return $result;
|
||||
}
|
||||
} elseif (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return ! is_null($this->farParent->{$this->localKey})
|
||||
? $this->get()
|
||||
: $this->related->newCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query as a "select" statement.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function get($columns = ['*'])
|
||||
{
|
||||
$builder = $this->prepareQueryBuilder($columns);
|
||||
|
||||
$models = $builder->getModels();
|
||||
|
||||
// If we actually found models we will also eager load any relationships that
|
||||
// have been specified as needing to be eager loaded. This will solve the
|
||||
// n + 1 query problem for the developer and also increase performance.
|
||||
if (count($models) > 0) {
|
||||
$models = $builder->eagerLoadRelations($models);
|
||||
}
|
||||
|
||||
return $this->related->newCollection($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a paginator for the "select" statement.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int $page
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$this->query->addSelect($this->shouldSelect($columns));
|
||||
|
||||
return $this->query->paginate($perPage, $columns, $pageName, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query into a simple paginator.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
* @return \Illuminate\Contracts\Pagination\Paginator
|
||||
*/
|
||||
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$this->query->addSelect($this->shouldSelect($columns));
|
||||
|
||||
return $this->query->simplePaginate($perPage, $columns, $pageName, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the select clause for the relation query.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return array
|
||||
*/
|
||||
protected function shouldSelect(array $columns = ['*'])
|
||||
{
|
||||
if ($columns == ['*']) {
|
||||
$columns = [$this->related->getTable().'.*'];
|
||||
}
|
||||
|
||||
return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk the results of the query.
|
||||
*
|
||||
* @param int $count
|
||||
* @param callable $callback
|
||||
* @return bool
|
||||
*/
|
||||
public function chunk($count, callable $callback)
|
||||
{
|
||||
return $this->prepareQueryBuilder()->chunk($count, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk the results of a query by comparing numeric IDs.
|
||||
*
|
||||
* @param int $count
|
||||
* @param callable $callback
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return bool
|
||||
*/
|
||||
public function chunkById($count, callable $callback, $column = null, $alias = null)
|
||||
{
|
||||
$column = $column ?? $this->getRelated()->getQualifiedKeyName();
|
||||
|
||||
$alias = $alias ?? $this->getRelated()->getKeyName();
|
||||
|
||||
return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a generator for the given query.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function cursor()
|
||||
{
|
||||
return $this->prepareQueryBuilder()->cursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item while chunking.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param int $count
|
||||
* @return bool
|
||||
*/
|
||||
public function each(callable $callback, $count = 1000)
|
||||
{
|
||||
return $this->chunk($count, function ($results) use ($callback) {
|
||||
foreach ($results as $key => $value) {
|
||||
if ($callback($value, $key) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunks of the given size.
|
||||
*
|
||||
* @param int $chunkSize
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazy($chunkSize = 1000)
|
||||
{
|
||||
return $this->prepareQueryBuilder()->lazy($chunkSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query lazily, by chunking the results of a query by comparing IDs.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|null $column
|
||||
* @param string|null $alias
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
|
||||
{
|
||||
$column = $column ?? $this->getRelated()->getQualifiedKeyName();
|
||||
|
||||
$alias = $alias ?? $this->getRelated()->getKeyName();
|
||||
|
||||
return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the query builder for query execution.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function prepareQueryBuilder($columns = ['*'])
|
||||
{
|
||||
$builder = $this->query->applyScopes();
|
||||
|
||||
return $builder->addSelect(
|
||||
$this->shouldSelect($builder->getQuery()->columns ? [] : $columns)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
if ($parentQuery->getQuery()->from === $query->getQuery()->from) {
|
||||
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) {
|
||||
return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
$this->performJoin($query);
|
||||
|
||||
return $query->select($columns)->whereColumn(
|
||||
$this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query on the same table.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
$query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
|
||||
|
||||
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey);
|
||||
|
||||
if ($this->throughParentSoftDeletes()) {
|
||||
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
|
||||
}
|
||||
|
||||
$query->getModel()->setTable($hash);
|
||||
|
||||
return $query->select($columns)->whereColumn(
|
||||
$parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query on the same table as the through parent.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
$table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash();
|
||||
|
||||
$query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName());
|
||||
|
||||
if ($this->throughParentSoftDeletes()) {
|
||||
$query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn());
|
||||
}
|
||||
|
||||
return $query->select($columns)->whereColumn(
|
||||
$parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified foreign key on the related model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedFarKeyName()
|
||||
{
|
||||
return $this->getQualifiedForeignKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key on the "through" model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFirstKeyName()
|
||||
{
|
||||
return $this->firstKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified foreign key on the "through" model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedFirstKeyName()
|
||||
{
|
||||
return $this->throughParent->qualifyColumn($this->firstKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key on the related model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignKeyName()
|
||||
{
|
||||
return $this->secondKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified foreign key on the related model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedForeignKeyName()
|
||||
{
|
||||
return $this->related->qualifyColumn($this->secondKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local key on the far parent model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalKeyName()
|
||||
{
|
||||
return $this->localKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the qualified local key on the far parent model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedLocalKeyName()
|
||||
{
|
||||
return $this->farParent->qualifyColumn($this->localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local key on the intermediary model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSecondLocalKeyName()
|
||||
{
|
||||
return $this->secondLocalKey;
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\SupportsPartialRelations;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\CanBeOneOfMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesRelatedModels;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
|
||||
class HasOne extends HasOneOrMany implements SupportsPartialRelations
|
||||
{
|
||||
use ComparesRelatedModels, CanBeOneOfMany, SupportsDefaultModels;
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
if (is_null($this->getParentKey())) {
|
||||
return $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
return $this->query->first() ?: $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->getDefaultFor($model));
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $this->matchOne($models, $results, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for an internal relationship existence query.
|
||||
*
|
||||
* Essentially, these queries compare on column names like "whereColumn".
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
if ($this->isOneOfMany()) {
|
||||
$this->mergeOneOfManyJoinsTo($query);
|
||||
}
|
||||
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add constraints for inner join subselect for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return void
|
||||
*/
|
||||
public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null)
|
||||
{
|
||||
$query->addSelect($this->foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should be selected by the one of many subquery.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function getOneOfManySubQuerySelectColumns()
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join query constraints for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\JoinClause $join
|
||||
* @return void
|
||||
*/
|
||||
public function addOneOfManyJoinSubQueryConstraints(JoinClause $join)
|
||||
{
|
||||
$join->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function newRelatedInstanceFor(Model $parent)
|
||||
{
|
||||
return $this->related->newInstance()->setAttribute(
|
||||
$this->getForeignKeyName(), $parent->{$this->localKey}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelatedKeyFrom(Model $model)
|
||||
{
|
||||
return $model->getAttribute($this->getForeignKeyName());
|
||||
}
|
||||
}
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
|
||||
abstract class HasOneOrMany extends Relation
|
||||
{
|
||||
use InteractsWithDictionary;
|
||||
|
||||
/**
|
||||
* The foreign key of the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foreignKey;
|
||||
|
||||
/**
|
||||
* The local key of the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $localKey;
|
||||
|
||||
/**
|
||||
* Create a new has one or many relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $localKey
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
|
||||
{
|
||||
$this->localKey = $localKey;
|
||||
$this->foreignKey = $foreignKey;
|
||||
|
||||
parent::__construct($query, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an un-saved instance of the related model.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function make(array $attributes = [])
|
||||
{
|
||||
return tap($this->related->newInstance($attributes), function ($instance) {
|
||||
$this->setForeignAttributesForCreate($instance);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an un-saved instance of the related models.
|
||||
*
|
||||
* @param iterable $records
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function makeMany($records)
|
||||
{
|
||||
$instances = $this->related->newCollection();
|
||||
|
||||
foreach ($records as $record) {
|
||||
$instances->push($this->make($record));
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base constraints on the relation query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addConstraints()
|
||||
{
|
||||
if (static::$constraints) {
|
||||
$query = $this->getRelationQuery();
|
||||
|
||||
$query->where($this->foreignKey, '=', $this->getParentKey());
|
||||
|
||||
$query->whereNotNull($this->foreignKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
$whereIn = $this->whereInMethod($this->parent, $this->localKey);
|
||||
|
||||
$this->getRelationQuery()->{$whereIn}(
|
||||
$this->foreignKey, $this->getKeys($models, $this->localKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their single parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function matchOne(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $this->matchOneOrMany($models, $results, $relation, 'one');
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their many parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function matchMany(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $this->matchOneOrMany($models, $results, $relation, 'many');
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their many parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
|
||||
{
|
||||
$dictionary = $this->buildDictionary($results);
|
||||
|
||||
// Once we have the dictionary we can simply spin through the parent models to
|
||||
// link them up with their children using the keyed dictionary to make the
|
||||
// matching very convenient and easy work. Then we'll just return them.
|
||||
foreach ($models as $model) {
|
||||
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
|
||||
$model->setRelation(
|
||||
$relation, $this->getRelationValue($dictionary, $key, $type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a relationship by one or many type.
|
||||
*
|
||||
* @param array $dictionary
|
||||
* @param string $key
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelationValue(array $dictionary, $key, $type)
|
||||
{
|
||||
$value = $dictionary[$key];
|
||||
|
||||
return $type === 'one' ? reset($value) : $this->related->newCollection($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build model dictionary keyed by the relation's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @return array
|
||||
*/
|
||||
protected function buildDictionary(Collection $results)
|
||||
{
|
||||
$foreign = $this->getForeignKeyName();
|
||||
|
||||
return $results->mapToDictionary(function ($result) use ($foreign) {
|
||||
return [$this->getDictionaryKey($result->{$foreign}) => $result];
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a model by its primary key or return a new instance of the related model.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function findOrNew($id, $columns = ['*'])
|
||||
{
|
||||
if (is_null($instance = $this->find($id, $columns))) {
|
||||
$instance = $this->related->newInstance();
|
||||
|
||||
$this->setForeignAttributesForCreate($instance);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first related model record matching the attributes or instantiate it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function firstOrNew(array $attributes = [], array $values = [])
|
||||
{
|
||||
if (is_null($instance = $this->where($attributes)->first())) {
|
||||
$instance = $this->related->newInstance(array_merge($attributes, $values));
|
||||
|
||||
$this->setForeignAttributesForCreate($instance);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first related record matching the attributes or create it.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function firstOrCreate(array $attributes = [], array $values = [])
|
||||
{
|
||||
if (is_null($instance = $this->where($attributes)->first())) {
|
||||
$instance = $this->create(array_merge($attributes, $values));
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a related record matching the attributes, and fill it with values.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $values
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function updateOrCreate(array $attributes, array $values = [])
|
||||
{
|
||||
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
|
||||
$instance->fill($values);
|
||||
|
||||
$instance->save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a model instance to the parent model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return \Illuminate\Database\Eloquent\Model|false
|
||||
*/
|
||||
public function save(Model $model)
|
||||
{
|
||||
$this->setForeignAttributesForCreate($model);
|
||||
|
||||
return $model->save() ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a collection of models to the parent instance.
|
||||
*
|
||||
* @param iterable $models
|
||||
* @return iterable
|
||||
*/
|
||||
public function saveMany($models)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$this->save($model);
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the related model.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function create(array $attributes = [])
|
||||
{
|
||||
return tap($this->related->newInstance($attributes), function ($instance) {
|
||||
$this->setForeignAttributesForCreate($instance);
|
||||
|
||||
$instance->save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Collection of new instances of the related model.
|
||||
*
|
||||
* @param iterable $records
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function createMany(iterable $records)
|
||||
{
|
||||
$instances = $this->related->newCollection();
|
||||
|
||||
foreach ($records as $record) {
|
||||
$instances->push($this->create($record));
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign ID for creating a related model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
protected function setForeignAttributesForCreate(Model $model)
|
||||
{
|
||||
$model->setAttribute($this->getForeignKeyName(), $this->getParentKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
if ($query->getQuery()->from == $parentQuery->getQuery()->from) {
|
||||
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship query on the same table.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
$query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
|
||||
|
||||
$query->getModel()->setTable($hash);
|
||||
|
||||
return $query->select($columns)->whereColumn(
|
||||
$this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->getForeignKeyName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key for comparing against the parent key in "has" query.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExistenceCompareKey()
|
||||
{
|
||||
return $this->getQualifiedForeignKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key value of the parent's local key.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParentKey()
|
||||
{
|
||||
return $this->parent->getAttribute($this->localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified parent key name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedParentKeyName()
|
||||
{
|
||||
return $this->parent->qualifyColumn($this->localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plain foreign key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignKeyName()
|
||||
{
|
||||
$segments = explode('.', $this->getQualifiedForeignKeyName());
|
||||
|
||||
return end($segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key for the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedForeignKeyName()
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local key for the relationship.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalKeyName()
|
||||
{
|
||||
return $this->localKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
|
||||
|
||||
class HasOneThrough extends HasManyThrough
|
||||
{
|
||||
use InteractsWithDictionary, SupportsDefaultModels;
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return $this->first() ?: $this->getDefaultFor($this->farParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->getDefaultFor($model));
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
$dictionary = $this->buildDictionary($results);
|
||||
|
||||
// Once we have the dictionary we can simply spin through the parent models to
|
||||
// link them up with their children using the keyed dictionary to make the
|
||||
// matching very convenient and easy work. Then we'll just return them.
|
||||
foreach ($models as $model) {
|
||||
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
|
||||
$value = $dictionary[$key];
|
||||
$model->setRelation(
|
||||
$relation, reset($value)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function newRelatedInstanceFor(Model $parent)
|
||||
{
|
||||
return $this->related->newInstance();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class MorphMany extends MorphOneOrMany
|
||||
{
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
return ! is_null($this->getParentKey())
|
||||
? $this->query->get()
|
||||
: $this->related->newCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->related->newCollection());
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $this->matchMany($models, $results, $relation);
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\SupportsPartialRelations;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\CanBeOneOfMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesRelatedModels;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
|
||||
class MorphOne extends MorphOneOrMany implements SupportsPartialRelations
|
||||
{
|
||||
use CanBeOneOfMany, ComparesRelatedModels, SupportsDefaultModels;
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
if (is_null($this->getParentKey())) {
|
||||
return $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
return $this->query->first() ?: $this->getDefaultFor($this->parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function initRelation(array $models, $relation)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$model->setRelation($relation, $this->getDefaultFor($model));
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $this->matchOne($models, $results, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
if ($this->isOneOfMany()) {
|
||||
$this->mergeOneOfManyJoinsTo($query);
|
||||
}
|
||||
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add constraints for inner join subselect for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string|null $column
|
||||
* @param string|null $aggregate
|
||||
* @return void
|
||||
*/
|
||||
public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null)
|
||||
{
|
||||
$query->addSelect($this->foreignKey, $this->morphType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the columns that should be selected by the one of many subquery.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function getOneOfManySubQuerySelectColumns()
|
||||
{
|
||||
return [$this->foreignKey, $this->morphType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join query constraints for one of many relationships.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\JoinClause $join
|
||||
* @return void
|
||||
*/
|
||||
public function addOneOfManyJoinSubQueryConstraints(JoinClause $join)
|
||||
{
|
||||
$join
|
||||
->on($this->qualifySubSelectColumn($this->morphType), '=', $this->qualifyRelatedColumn($this->morphType))
|
||||
->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function newRelatedInstanceFor(Model $parent)
|
||||
{
|
||||
return $this->related->newInstance()
|
||||
->setAttribute($this->getForeignKeyName(), $parent->{$this->localKey})
|
||||
->setAttribute($this->getMorphType(), $this->morphClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the model's foreign key.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelatedKeyFrom(Model $model)
|
||||
{
|
||||
return $model->getAttribute($this->getForeignKeyName());
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
abstract class MorphOneOrMany extends HasOneOrMany
|
||||
{
|
||||
/**
|
||||
* The foreign key type for the relationship.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* The class name of the parent model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphClass;
|
||||
|
||||
/**
|
||||
* Create a new morph one or many relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
* @param string $localKey
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
|
||||
{
|
||||
$this->morphType = $type;
|
||||
|
||||
$this->morphClass = $parent->getMorphClass();
|
||||
|
||||
parent::__construct($query, $parent, $id, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base constraints on the relation query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addConstraints()
|
||||
{
|
||||
if (static::$constraints) {
|
||||
parent::addConstraints();
|
||||
|
||||
$this->getRelationQuery()->where($this->morphType, $this->morphClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
parent::addEagerConstraints($models);
|
||||
|
||||
$this->getRelationQuery()->where($this->morphType, $this->morphClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign ID and type for creating a related model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
protected function setForeignAttributesForCreate(Model $model)
|
||||
{
|
||||
$model->{$this->getForeignKeyName()} = $this->getParentKey();
|
||||
|
||||
$model->{$this->getMorphType()} = $this->morphClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relationship query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where(
|
||||
$query->qualifyColumn($this->getMorphType()), $this->morphClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key "type" name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedMorphType()
|
||||
{
|
||||
return $this->morphType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plain morph type name without the table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphType()
|
||||
{
|
||||
return last(explode('.', $this->morphType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name of the parent model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return $this->morphClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MorphPivot extends Pivot
|
||||
{
|
||||
/**
|
||||
* The type of the polymorphic relation.
|
||||
*
|
||||
* Explicitly define this so it's not included in saved attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* The value of the polymorphic relation.
|
||||
*
|
||||
* Explicitly define this so it's not included in saved attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphClass;
|
||||
|
||||
/**
|
||||
* Set the keys for a save update query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSaveQuery($query)
|
||||
{
|
||||
$query->where($this->morphType, $this->morphClass);
|
||||
|
||||
return parent::setKeysForSaveQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a select query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function setKeysForSelectQuery($query)
|
||||
{
|
||||
$query->where($this->morphType, $this->morphClass);
|
||||
|
||||
return parent::setKeysForSelectQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the pivot model record from the database.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if (isset($this->attributes[$this->getKeyName()])) {
|
||||
return (int) parent::delete();
|
||||
}
|
||||
|
||||
if ($this->fireModelEvent('deleting') === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = $this->getDeleteQuery();
|
||||
|
||||
$query->where($this->morphType, $this->morphClass);
|
||||
|
||||
return tap($query->delete(), function () {
|
||||
$this->fireModelEvent('deleted', false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the morph type for the pivot.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphType()
|
||||
{
|
||||
return $this->morphType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the morph type for the pivot.
|
||||
*
|
||||
* @param string $morphType
|
||||
* @return $this
|
||||
*/
|
||||
public function setMorphType($morphType)
|
||||
{
|
||||
$this->morphType = $morphType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the morph class for the pivot.
|
||||
*
|
||||
* @param string $morphClass
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphPivot
|
||||
*/
|
||||
public function setMorphClass($morphClass)
|
||||
{
|
||||
$this->morphClass = $morphClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queueable identity for the entity.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getQueueableId()
|
||||
{
|
||||
if (isset($this->attributes[$this->getKeyName()])) {
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s:%s:%s:%s:%s:%s',
|
||||
$this->foreignKey, $this->getAttribute($this->foreignKey),
|
||||
$this->relatedKey, $this->getAttribute($this->relatedKey),
|
||||
$this->morphType, $this->morphClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query to restore one or more models by their queueable IDs.
|
||||
*
|
||||
* @param array|int $ids
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function newQueryForRestoration($ids)
|
||||
{
|
||||
if (is_array($ids)) {
|
||||
return $this->newQueryForCollectionRestoration($ids);
|
||||
}
|
||||
|
||||
if (! Str::contains($ids, ':')) {
|
||||
return parent::newQueryForRestoration($ids);
|
||||
}
|
||||
|
||||
$segments = explode(':', $ids);
|
||||
|
||||
return $this->newQueryWithoutScopes()
|
||||
->where($segments[0], $segments[1])
|
||||
->where($segments[2], $segments[3])
|
||||
->where($segments[4], $segments[5]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new query to restore multiple models by their queueable IDs.
|
||||
*
|
||||
* @param array $ids
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function newQueryForCollectionRestoration(array $ids)
|
||||
{
|
||||
$ids = array_values($ids);
|
||||
|
||||
if (! Str::contains($ids[0], ':')) {
|
||||
return parent::newQueryForRestoration($ids);
|
||||
}
|
||||
|
||||
$query = $this->newQueryWithoutScopes();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$segments = explode(':', $id);
|
||||
|
||||
$query->orWhere(function ($query) use ($segments) {
|
||||
return $query->where($segments[0], $segments[1])
|
||||
->where($segments[2], $segments[3])
|
||||
->where($segments[4], $segments[5]);
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
||||
|
||||
class MorphTo extends BelongsTo
|
||||
{
|
||||
use InteractsWithDictionary;
|
||||
|
||||
/**
|
||||
* The type of the polymorphic relation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* The models whose relations are being eager loaded.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
protected $models;
|
||||
|
||||
/**
|
||||
* All of the models keyed by ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dictionary = [];
|
||||
|
||||
/**
|
||||
* A buffer of dynamic calls to query macros.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $macroBuffer = [];
|
||||
|
||||
/**
|
||||
* A map of relations to load for each individual morph type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $morphableEagerLoads = [];
|
||||
|
||||
/**
|
||||
* A map of relationship counts to load for each individual morph type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $morphableEagerLoadCounts = [];
|
||||
|
||||
/**
|
||||
* A map of constraints to apply for each individual morph type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $morphableConstraints = [];
|
||||
|
||||
/**
|
||||
* Create a new morph to relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $foreignKey
|
||||
* @param string $ownerKey
|
||||
* @param string $type
|
||||
* @param string $relation
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
|
||||
{
|
||||
$this->morphType = $type;
|
||||
|
||||
parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
$this->buildDictionary($this->models = Collection::make($models));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a dictionary with the models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $models
|
||||
* @return void
|
||||
*/
|
||||
protected function buildDictionary(Collection $models)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
if ($model->{$this->morphType}) {
|
||||
$morphTypeKey = $this->getDictionaryKey($model->{$this->morphType});
|
||||
$foreignKeyKey = $this->getDictionaryKey($model->{$this->foreignKey});
|
||||
|
||||
$this->dictionary[$morphTypeKey][$foreignKeyKey][] = $model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* Called via eager load method of Eloquent query builder.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getEager()
|
||||
{
|
||||
foreach (array_keys($this->dictionary) as $type) {
|
||||
$this->matchToMorphParents($type, $this->getResultsByType($type));
|
||||
}
|
||||
|
||||
return $this->models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the relation results for a type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
protected function getResultsByType($type)
|
||||
{
|
||||
$instance = $this->createModelByType($type);
|
||||
|
||||
$ownerKey = $this->ownerKey ?? $instance->getKeyName();
|
||||
|
||||
$query = $this->replayMacros($instance->newQuery())
|
||||
->mergeConstraintsFrom($this->getQuery())
|
||||
->with(array_merge(
|
||||
$this->getQuery()->getEagerLoads(),
|
||||
(array) ($this->morphableEagerLoads[get_class($instance)] ?? [])
|
||||
))
|
||||
->withCount(
|
||||
(array) ($this->morphableEagerLoadCounts[get_class($instance)] ?? [])
|
||||
);
|
||||
|
||||
if ($callback = ($this->morphableConstraints[get_class($instance)] ?? null)) {
|
||||
$callback($query);
|
||||
}
|
||||
|
||||
$whereIn = $this->whereInMethod($instance, $ownerKey);
|
||||
|
||||
return $query->{$whereIn}(
|
||||
$instance->getTable().'.'.$ownerKey, $this->gatherKeysByType($type, $instance->getKeyType())
|
||||
)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all of the foreign keys for a given type.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $keyType
|
||||
* @return array
|
||||
*/
|
||||
protected function gatherKeysByType($type, $keyType)
|
||||
{
|
||||
return $keyType !== 'string'
|
||||
? array_keys($this->dictionary[$type])
|
||||
: array_map(function ($modelId) {
|
||||
return (string) $modelId;
|
||||
}, array_filter(array_keys($this->dictionary[$type])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new model instance by type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function createModelByType($type)
|
||||
{
|
||||
$class = Model::getActualClassNameForMorph($type);
|
||||
|
||||
return tap(new $class, function ($instance) {
|
||||
if (! $instance->getConnectionName()) {
|
||||
$instance->setConnection($this->getConnection()->getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
public function match(array $models, Collection $results, $relation)
|
||||
{
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the results for a given type to their parents.
|
||||
*
|
||||
* @param string $type
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @return void
|
||||
*/
|
||||
protected function matchToMorphParents($type, Collection $results)
|
||||
{
|
||||
foreach ($results as $result) {
|
||||
$ownerKey = ! is_null($this->ownerKey) ? $this->getDictionaryKey($result->{$this->ownerKey}) : $result->getKey();
|
||||
|
||||
if (isset($this->dictionary[$type][$ownerKey])) {
|
||||
foreach ($this->dictionary[$type][$ownerKey] as $model) {
|
||||
$model->setRelation($this->relationName, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the model instance to the given parent.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function associate($model)
|
||||
{
|
||||
if ($model instanceof Model) {
|
||||
$foreignKey = $this->ownerKey && $model->{$this->ownerKey}
|
||||
? $this->ownerKey
|
||||
: $model->getKeyName();
|
||||
}
|
||||
|
||||
$this->parent->setAttribute(
|
||||
$this->foreignKey, $model instanceof Model ? $model->{$foreignKey} : null
|
||||
);
|
||||
|
||||
$this->parent->setAttribute(
|
||||
$this->morphType, $model instanceof Model ? $model->getMorphClass() : null
|
||||
);
|
||||
|
||||
return $this->parent->setRelation($this->relationName, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dissociate previously associated model from the given parent.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function dissociate()
|
||||
{
|
||||
$this->parent->setAttribute($this->foreignKey, null);
|
||||
|
||||
$this->parent->setAttribute($this->morphType, null);
|
||||
|
||||
return $this->parent->setRelation($this->relationName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch all of the related models for the relationship.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function touch()
|
||||
{
|
||||
if (! is_null($this->child->{$this->foreignKey})) {
|
||||
parent::touch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new related instance for the given model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected function newRelatedInstanceFor(Model $parent)
|
||||
{
|
||||
return $parent->{$this->getRelationName()}()->getRelated()->newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key "type" name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphType()
|
||||
{
|
||||
return $this->morphType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dictionary used by the relationship.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDictionary()
|
||||
{
|
||||
return $this->dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify which relations to load for a given morph type.
|
||||
*
|
||||
* @param array $with
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function morphWith(array $with)
|
||||
{
|
||||
$this->morphableEagerLoads = array_merge(
|
||||
$this->morphableEagerLoads, $with
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify which relationship counts to load for a given morph type.
|
||||
*
|
||||
* @param array $withCount
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function morphWithCount(array $withCount)
|
||||
{
|
||||
$this->morphableEagerLoadCounts = array_merge(
|
||||
$this->morphableEagerLoadCounts, $withCount
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify constraints on the query for a given morph type.
|
||||
*
|
||||
* @param array $callbacks
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function constrain(array $callbacks)
|
||||
{
|
||||
$this->morphableConstraints = array_merge(
|
||||
$this->morphableConstraints, $callbacks
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replay stored macro calls on the actual related instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function replayMacros(Builder $query)
|
||||
{
|
||||
foreach ($this->macroBuffer as $macro) {
|
||||
$query->{$macro['method']}(...$macro['parameters']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic method calls to the relationship.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
try {
|
||||
$result = parent::__call($method, $parameters);
|
||||
|
||||
if (in_array($method, ['select', 'selectRaw', 'selectSub', 'addSelect', 'withoutGlobalScopes'])) {
|
||||
$this->macroBuffer[] = compact('method', 'parameters');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// If we tried to call a method that does not exist on the parent Builder instance,
|
||||
// we'll assume that we want to call a query macro (e.g. withTrashed) that only
|
||||
// exists on related models. We will just store the call and replay it later.
|
||||
catch (BadMethodCallException $e) {
|
||||
$this->macroBuffer[] = compact('method', 'parameters');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class MorphToMany extends BelongsToMany
|
||||
{
|
||||
/**
|
||||
* The type of the polymorphic relation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* The class name of the morph type constraint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphClass;
|
||||
|
||||
/**
|
||||
* Indicates if we are connecting the inverse of the relation.
|
||||
*
|
||||
* This primarily affects the morphClass constraint.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $inverse;
|
||||
|
||||
/**
|
||||
* Create a new morph to many relationship instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param string $name
|
||||
* @param string $table
|
||||
* @param string $foreignPivotKey
|
||||
* @param string $relatedPivotKey
|
||||
* @param string $parentKey
|
||||
* @param string $relatedKey
|
||||
* @param string|null $relationName
|
||||
* @param bool $inverse
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent, $name, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey, $relationName = null, $inverse = false)
|
||||
{
|
||||
$this->inverse = $inverse;
|
||||
$this->morphType = $name.'_type';
|
||||
$this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass();
|
||||
|
||||
parent::__construct(
|
||||
$query, $parent, $table, $foreignPivotKey,
|
||||
$relatedPivotKey, $parentKey, $relatedKey, $relationName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the where clause for the relation query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function addWhereConstraints()
|
||||
{
|
||||
parent::addWhereConstraints();
|
||||
|
||||
$this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
public function addEagerConstraints(array $models)
|
||||
{
|
||||
parent::addEagerConstraints($models);
|
||||
|
||||
$this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pivot attachment record.
|
||||
*
|
||||
* @param int $id
|
||||
* @param bool $timed
|
||||
* @return array
|
||||
*/
|
||||
protected function baseAttachRecord($id, $timed)
|
||||
{
|
||||
return Arr::add(
|
||||
parent::baseAttachRecord($id, $timed), $this->morphType, $this->morphClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship count query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where(
|
||||
$this->qualifyPivotColumn($this->morphType), $this->morphClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot models that are currently attached.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getCurrentlyAttachedPivots()
|
||||
{
|
||||
return parent::getCurrentlyAttachedPivots()->map(function ($record) {
|
||||
return $record instanceof MorphPivot
|
||||
? $record->setMorphType($this->morphType)
|
||||
->setMorphClass($this->morphClass)
|
||||
: $record;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new query builder for the pivot table.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function newPivotQuery()
|
||||
{
|
||||
return parent::newPivotQuery()->where($this->morphType, $this->morphClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pivot model instance.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param bool $exists
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Pivot
|
||||
*/
|
||||
public function newPivot(array $attributes = [], $exists = false)
|
||||
{
|
||||
$using = $this->using;
|
||||
|
||||
$pivot = $using ? $using::fromRawAttributes($this->parent, $attributes, $this->table, $exists)
|
||||
: MorphPivot::fromAttributes($this->parent, $attributes, $this->table, $exists);
|
||||
|
||||
$pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey)
|
||||
->setMorphType($this->morphType)
|
||||
->setMorphClass($this->morphClass);
|
||||
|
||||
return $pivot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot columns for the relation.
|
||||
*
|
||||
* "pivot_" is prefixed at each column for easy removal later.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function aliasedPivotColumns()
|
||||
{
|
||||
$defaults = [$this->foreignPivotKey, $this->relatedPivotKey, $this->morphType];
|
||||
|
||||
return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
|
||||
return $this->qualifyPivotColumn($column).' as pivot_'.$column;
|
||||
})->unique()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the foreign key "type" name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphType()
|
||||
{
|
||||
return $this->morphType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name of the parent model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphClass()
|
||||
{
|
||||
return $this->morphClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the indicator for a reverse relationship.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getInverse()
|
||||
{
|
||||
return $this->inverse;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
|
||||
|
||||
class Pivot extends Model
|
||||
{
|
||||
use AsPivot;
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\MultipleRecordsFoundException;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
abstract class Relation
|
||||
{
|
||||
use ForwardsCalls, Macroable {
|
||||
__call as macroCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Eloquent query builder instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* The parent model instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* The related model instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
protected $related;
|
||||
|
||||
/**
|
||||
* Indicates if the relation is adding constraints.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $constraints = true;
|
||||
|
||||
/**
|
||||
* An array to map class names to their morph names in the database.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $morphMap = [];
|
||||
|
||||
/**
|
||||
* Prevents morph relationships without a morph map.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $requireMorphMap = false;
|
||||
|
||||
/**
|
||||
* The count of self joins.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $selfJoinCount = 0;
|
||||
|
||||
/**
|
||||
* Create a new relation instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Builder $query, Model $parent)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->parent = $parent;
|
||||
$this->related = $query->getModel();
|
||||
|
||||
$this->addConstraints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback with constraints disabled on the relation.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function noConstraints(Closure $callback)
|
||||
{
|
||||
$previous = static::$constraints;
|
||||
|
||||
static::$constraints = false;
|
||||
|
||||
// When resetting the relation where clause, we want to shift the first element
|
||||
// off of the bindings, leaving only the constraints that the developers put
|
||||
// as "extra" on the relationships, and not original relation constraints.
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
static::$constraints = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base constraints on the relation query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function addConstraints();
|
||||
|
||||
/**
|
||||
* Set the constraints for an eager load of the relation.
|
||||
*
|
||||
* @param array $models
|
||||
* @return void
|
||||
*/
|
||||
abstract public function addEagerConstraints(array $models);
|
||||
|
||||
/**
|
||||
* Initialize the relation on a set of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
abstract public function initRelation(array $models, $relation);
|
||||
|
||||
/**
|
||||
* Match the eagerly loaded results to their parents.
|
||||
*
|
||||
* @param array $models
|
||||
* @param \Illuminate\Database\Eloquent\Collection $results
|
||||
* @param string $relation
|
||||
* @return array
|
||||
*/
|
||||
abstract public function match(array $models, Collection $results, $relation);
|
||||
|
||||
/**
|
||||
* Get the results of the relationship.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function getResults();
|
||||
|
||||
/**
|
||||
* Get the relationship for eager loading.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function getEager()
|
||||
{
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query and get the first result if it's the sole matching record.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @throws \Illuminate\Database\MultipleRecordsFoundException
|
||||
*/
|
||||
public function sole($columns = ['*'])
|
||||
{
|
||||
$result = $this->take(2)->get($columns);
|
||||
|
||||
if ($result->isEmpty()) {
|
||||
throw (new ModelNotFoundException)->setModel(get_class($this->related));
|
||||
}
|
||||
|
||||
if ($result->count() > 1) {
|
||||
throw new MultipleRecordsFoundException;
|
||||
}
|
||||
|
||||
return $result->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query as a "select" statement.
|
||||
*
|
||||
* @param array $columns
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function get($columns = ['*'])
|
||||
{
|
||||
return $this->query->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch all of the related models for the relationship.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function touch()
|
||||
{
|
||||
$model = $this->getRelated();
|
||||
|
||||
if (! $model::isIgnoringTouch()) {
|
||||
$this->rawUpdate([
|
||||
$model->getUpdatedAtColumn() => $model->freshTimestampString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a raw update against the base query.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return int
|
||||
*/
|
||||
public function rawUpdate(array $attributes = [])
|
||||
{
|
||||
return $this->query->withoutGlobalScopes()->update($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for a relationship count query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery)
|
||||
{
|
||||
return $this->getRelationExistenceQuery(
|
||||
$query, $parentQuery, new Expression('count(*)')
|
||||
)->setBindings([], 'select');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the constraints for an internal relationship existence query.
|
||||
*
|
||||
* Essentially, these queries compare on column names like whereColumn.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
||||
* @param array|mixed $columns
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
||||
{
|
||||
return $query->select($columns)->whereColumn(
|
||||
$this->getQualifiedParentKeyName(), '=', $this->getExistenceCompareKey()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relationship join table hash.
|
||||
*
|
||||
* @param bool $incrementJoinCount
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountHash($incrementJoinCount = true)
|
||||
{
|
||||
return 'laravel_reserved_'.($incrementJoinCount ? static::$selfJoinCount++ : static::$selfJoinCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the primary keys for an array of models.
|
||||
*
|
||||
* @param array $models
|
||||
* @param string|null $key
|
||||
* @return array
|
||||
*/
|
||||
protected function getKeys(array $models, $key = null)
|
||||
{
|
||||
return collect($models)->map(function ($value) use ($key) {
|
||||
return $key ? $value->getAttribute($key) : $value->getKey();
|
||||
})->values()->unique(null, true)->sort()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query builder that will contain the relationship constraints.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function getRelationQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying query for the relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base query builder driving the Eloquent builder.
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function getBaseQuery()
|
||||
{
|
||||
return $this->query->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent model of the relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified parent key name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedParentKeyName()
|
||||
{
|
||||
return $this->parent->getQualifiedKeyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the related model of the relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
public function getRelated()
|
||||
{
|
||||
return $this->related;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "created at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createdAt()
|
||||
{
|
||||
return $this->parent->getCreatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "updated at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function updatedAt()
|
||||
{
|
||||
return $this->parent->getUpdatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the related model's "updated at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function relatedUpdatedAt()
|
||||
{
|
||||
return $this->related->getUpdatedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "where in" method for eager loading.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function whereInMethod(Model $model, $key)
|
||||
{
|
||||
return $model->getKeyName() === last(explode('.', $key))
|
||||
&& in_array($model->getKeyType(), ['int', 'integer'])
|
||||
? 'whereIntegerInRaw'
|
||||
: 'whereIn';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent polymorphic relationships from being used without model mappings.
|
||||
*
|
||||
* @param bool $requireMorphMap
|
||||
* @return void
|
||||
*/
|
||||
public static function requireMorphMap($requireMorphMap = true)
|
||||
{
|
||||
static::$requireMorphMap = $requireMorphMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if polymorphic relationships require explicit model mapping.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function requiresMorphMap()
|
||||
{
|
||||
return static::$requireMorphMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the morph map for polymorphic relations and require all morphed models to be explicitly mapped.
|
||||
*
|
||||
* @param array|null $map
|
||||
* @param bool $merge
|
||||
* @return array
|
||||
*/
|
||||
public static function enforceMorphMap(array $map, $merge = true)
|
||||
{
|
||||
static::requireMorphMap();
|
||||
|
||||
return static::morphMap($map, $merge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or get the morph map for polymorphic relations.
|
||||
*
|
||||
* @param array|null $map
|
||||
* @param bool $merge
|
||||
* @return array
|
||||
*/
|
||||
public static function morphMap(array $map = null, $merge = true)
|
||||
{
|
||||
$map = static::buildMorphMapFromModels($map);
|
||||
|
||||
if (is_array($map)) {
|
||||
static::$morphMap = $merge && static::$morphMap
|
||||
? $map + static::$morphMap : $map;
|
||||
}
|
||||
|
||||
return static::$morphMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a table-keyed array from model class names.
|
||||
*
|
||||
* @param string[]|null $models
|
||||
* @return array|null
|
||||
*/
|
||||
protected static function buildMorphMapFromModels(array $models = null)
|
||||
{
|
||||
if (is_null($models) || Arr::isAssoc($models)) {
|
||||
return $models;
|
||||
}
|
||||
|
||||
return array_combine(array_map(function ($model) {
|
||||
return (new $model)->getTable();
|
||||
}, $models), $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model associated with a custom polymorphic type.
|
||||
*
|
||||
* @param string $alias
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getMorphedModel($alias)
|
||||
{
|
||||
return static::$morphMap[$alias] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic method calls to the relationship.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (static::hasMacro($method)) {
|
||||
return $this->macroCall($method, $parameters);
|
||||
}
|
||||
|
||||
return $this->forwardDecoratedCallTo($this->query, $method, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a clone of the underlying query builder when cloning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->query = clone $this->query;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
interface Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model);
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
/**
|
||||
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withTrashed(bool $withTrashed = true)
|
||||
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyTrashed()
|
||||
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutTrashed()
|
||||
*/
|
||||
trait SoftDeletes
|
||||
{
|
||||
/**
|
||||
* Indicates if the model is currently force deleting.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $forceDeleting = false;
|
||||
|
||||
/**
|
||||
* Boot the soft deleting trait for a model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootSoftDeletes()
|
||||
{
|
||||
static::addGlobalScope(new SoftDeletingScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the soft deleting trait for an instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initializeSoftDeletes()
|
||||
{
|
||||
if (! isset($this->casts[$this->getDeletedAtColumn()])) {
|
||||
$this->casts[$this->getDeletedAtColumn()] = 'datetime';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a hard delete on a soft deleted model.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function forceDelete()
|
||||
{
|
||||
$this->forceDeleting = true;
|
||||
|
||||
return tap($this->delete(), function ($deleted) {
|
||||
$this->forceDeleting = false;
|
||||
|
||||
if ($deleted) {
|
||||
$this->fireModelEvent('forceDeleted', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual delete query on this model instance.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function performDeleteOnModel()
|
||||
{
|
||||
if ($this->forceDeleting) {
|
||||
$this->exists = false;
|
||||
|
||||
return $this->setKeysForSaveQuery($this->newModelQuery())->forceDelete();
|
||||
}
|
||||
|
||||
return $this->runSoftDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual delete query on this model instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function runSoftDelete()
|
||||
{
|
||||
$query = $this->setKeysForSaveQuery($this->newModelQuery());
|
||||
|
||||
$time = $this->freshTimestamp();
|
||||
|
||||
$columns = [$this->getDeletedAtColumn() => $this->fromDateTime($time)];
|
||||
|
||||
$this->{$this->getDeletedAtColumn()} = $time;
|
||||
|
||||
if ($this->timestamps && ! is_null($this->getUpdatedAtColumn())) {
|
||||
$this->{$this->getUpdatedAtColumn()} = $time;
|
||||
|
||||
$columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time);
|
||||
}
|
||||
|
||||
$query->update($columns);
|
||||
|
||||
$this->syncOriginalAttributes(array_keys($columns));
|
||||
|
||||
$this->fireModelEvent('trashed', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a soft-deleted model instance.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function restore()
|
||||
{
|
||||
// If the restoring event does not return false, we will proceed with this
|
||||
// restore operation. Otherwise, we bail out so the developer will stop
|
||||
// the restore totally. We will clear the deleted timestamp and save.
|
||||
if ($this->fireModelEvent('restoring') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->{$this->getDeletedAtColumn()} = null;
|
||||
|
||||
// Once we have saved the model, we will fire the "restored" event so this
|
||||
// developer will do anything they need to after a restore operation is
|
||||
// totally finished. Then we will return the result of the save call.
|
||||
$this->exists = true;
|
||||
|
||||
$result = $this->save();
|
||||
|
||||
$this->fireModelEvent('restored', false);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model instance has been soft-deleted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function trashed()
|
||||
{
|
||||
return ! is_null($this->{$this->getDeletedAtColumn()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a "softDeleted" model event callback with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function softDeleted($callback)
|
||||
{
|
||||
static::registerModelEvent('trashed', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a "restoring" model event callback with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function restoring($callback)
|
||||
{
|
||||
static::registerModelEvent('restoring', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a "restored" model event callback with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function restored($callback)
|
||||
{
|
||||
static::registerModelEvent('restored', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a "forceDeleted" model event callback with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function forceDeleted($callback)
|
||||
{
|
||||
static::registerModelEvent('forceDeleted', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is currently force deleting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isForceDeleting()
|
||||
{
|
||||
return $this->forceDeleting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "deleted at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDeletedAtColumn()
|
||||
{
|
||||
return defined('static::DELETED_AT') ? static::DELETED_AT : 'deleted_at';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified "deleted at" column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedDeletedAtColumn()
|
||||
{
|
||||
return $this->qualifyColumn($this->getDeletedAtColumn());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent;
|
||||
|
||||
class SoftDeletingScope implements Scope
|
||||
{
|
||||
/**
|
||||
* All of the extensions to be added to the builder.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $extensions = ['Restore', 'WithTrashed', 'WithoutTrashed', 'OnlyTrashed'];
|
||||
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return void
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
$builder->whereNull($model->getQualifiedDeletedAtColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the query builder with the needed functions.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
public function extend(Builder $builder)
|
||||
{
|
||||
foreach ($this->extensions as $extension) {
|
||||
$this->{"add{$extension}"}($builder);
|
||||
}
|
||||
|
||||
$builder->onDelete(function (Builder $builder) {
|
||||
$column = $this->getDeletedAtColumn($builder);
|
||||
|
||||
return $builder->update([
|
||||
$column => $builder->getModel()->freshTimestampString(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "deleted at" column for the builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return string
|
||||
*/
|
||||
protected function getDeletedAtColumn(Builder $builder)
|
||||
{
|
||||
if (count((array) $builder->getQuery()->joins) > 0) {
|
||||
return $builder->getModel()->getQualifiedDeletedAtColumn();
|
||||
}
|
||||
|
||||
return $builder->getModel()->getDeletedAtColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the restore extension to the builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
protected function addRestore(Builder $builder)
|
||||
{
|
||||
$builder->macro('restore', function (Builder $builder) {
|
||||
$builder->withTrashed();
|
||||
|
||||
return $builder->update([$builder->getModel()->getDeletedAtColumn() => null]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the with-trashed extension to the builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
protected function addWithTrashed(Builder $builder)
|
||||
{
|
||||
$builder->macro('withTrashed', function (Builder $builder, $withTrashed = true) {
|
||||
if (! $withTrashed) {
|
||||
return $builder->withoutTrashed();
|
||||
}
|
||||
|
||||
return $builder->withoutGlobalScope($this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the without-trashed extension to the builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
protected function addWithoutTrashed(Builder $builder)
|
||||
{
|
||||
$builder->macro('withoutTrashed', function (Builder $builder) {
|
||||
$model = $builder->getModel();
|
||||
|
||||
$builder->withoutGlobalScope($this)->whereNull(
|
||||
$model->getQualifiedDeletedAtColumn()
|
||||
);
|
||||
|
||||
return $builder;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the only-trashed extension to the builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
protected function addOnlyTrashed(Builder $builder)
|
||||
{
|
||||
$builder->macro('onlyTrashed', function (Builder $builder) {
|
||||
$model = $builder->getModel();
|
||||
|
||||
$builder->withoutGlobalScope($this)->whereNotNull(
|
||||
$model->getQualifiedDeletedAtColumn()
|
||||
);
|
||||
|
||||
return $builder;
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user