This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

View File

@@ -3,11 +3,23 @@
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.
*
@@ -48,12 +60,34 @@ trait BuildsQueries
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)
{
@@ -83,6 +117,8 @@ trait BuildsQueries
$lastId = null;
$page = 1;
do {
$clone = clone $this;
@@ -100,13 +136,19 @@ trait BuildsQueries
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
if ($callback($results) === false) {
if ($callback($results, $page) === false) {
return false;
}
$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;
@@ -123,15 +165,124 @@ trait BuildsQueries
*/
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
{
return $this->chunkById($count, function ($results) use ($callback) {
return $this->chunkById($count, function ($results, $page) use ($callback, $count) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
if ($callback($value, (($page - 1) * $count) + $key) === false) {
return false;
}
}
}, $column, $alias);
}
/**
* Query lazily, by chunks of the given size.
*
* @param int $chunkSize
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazy($chunkSize = 1000)
{
if ($chunkSize < 1) {
throw new InvalidArgumentException('The chunk size should be at least 1');
}
$this->enforceOrderBy();
return LazyCollection::make(function () use ($chunkSize) {
$page = 1;
while (true) {
$results = $this->forPage($page++, $chunkSize)->get();
foreach ($results as $result) {
yield $result;
}
if ($results->count() < $chunkSize) {
return;
}
}
});
}
/**
* Query lazily, by chunking the results of a query by comparing IDs.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
{
return $this->orderedLazyById($chunkSize, $column, $alias);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in descending order.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
{
return $this->orderedLazyById($chunkSize, $column, $alias, true);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in a given order.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @param bool $descending
* @return \Illuminate\Support\LazyCollection
*
* @throws \InvalidArgumentException
*/
protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false)
{
if ($chunkSize < 1) {
throw new InvalidArgumentException('The chunk size should be at least 1');
}
$column = $column ?? $this->defaultKeyName();
$alias = $alias ?? $column;
return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) {
$lastId = null;
while (true) {
$clone = clone $this;
if ($descending) {
$results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get();
} else {
$results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
}
foreach ($results as $result) {
yield $result;
}
if ($results->count() < $chunkSize) {
return;
}
$lastId = $results->last()->{$alias};
}
});
}
/**
* Execute the query and get the first result.
*
@@ -144,52 +295,143 @@ trait BuildsQueries
}
/**
* Apply the callback's query changes if the given "value" is true.
* Execute the query and get the first result if it's the sole matching record.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model|object|static|null
*
* @throws \Illuminate\Database\RecordsNotFoundException
* @throws \Illuminate\Database\MultipleRecordsFoundException
*/
public function when($value, $callback, $default = null)
public function sole($columns = ['*'])
{
if ($value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
$result = $this->take(2)->get($columns);
if ($result->isEmpty()) {
throw new RecordsNotFoundException;
}
return $this;
if ($result->count() > 1) {
throw new MultipleRecordsFoundException;
}
return $result->first();
}
/**
* Pass the query to a given callback.
* Paginate the given query using a cursor paginator.
*
* @param callable $callback
* @return $this
* @param int $perPage
* @param array $columns
* @param string $cursorName
* @param \Illuminate\Pagination\Cursor|string|null $cursor
* @return \Illuminate\Contracts\Pagination\CursorPaginator
*/
public function tap($callback)
protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
{
return $this->when(true, $callback);
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) {
$unionBuilders = isset($builder->unions) ? collect($builder->unions)->pluck('query') : collect();
if (! is_null($previousColumn)) {
$builder->where(
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
'=',
$cursor->parameter($previousColumn)
);
$unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) {
$unionBuilder->where(
$this->getOriginalColumnNameForCursorPagination($this, $previousColumn),
'=',
$cursor->parameter($previousColumn)
);
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
});
}
$builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) {
['column' => $column, 'direction' => $direction] = $orders[$i];
$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);
});
}
$unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
$unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) {
$unionBuilder->where(
$this->getOriginalColumnNameForCursorPagination($this, $column),
$direction === 'asc' ? '>' : '<',
$cursor->parameter($column)
);
if ($i < $orders->count() - 1) {
$unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) {
$addCursorConditions($builder, $column, $i + 1);
});
}
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union');
});
});
});
};
$addCursorConditions($this, null, 0);
}
$this->limit($perPage + 1);
return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [
'path' => Paginator::resolveCurrentPath(),
'cursorName' => $cursorName,
'parameters' => $orders->pluck('column')->toArray(),
]);
}
/**
* Apply the callback's query changes if the given "value" is false.
* Get the original column name of the given column, without any aliasing.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
* @param string $parameter
* @return string
*/
public function unless($value, $callback, $default = null)
protected function getOriginalColumnNameForCursorPagination($builder, string $parameter)
{
if (! $value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
$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 $this;
return $parameter;
}
/**
@@ -224,4 +466,31 @@ trait BuildsQueries
'items', 'perPage', 'currentPage', 'options'
));
}
/**
* Create a new cursor paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param int $perPage
* @param \Illuminate\Pagination\Cursor $cursor
* @param array $options
* @return \Illuminate\Pagination\CursorPaginator
*/
protected function cursorPaginator($items, $perPage, $cursor, $options)
{
return Container::getInstance()->makeWith(CursorPaginator::class, compact(
'items', 'perPage', 'cursor', 'options'
));
}
/**
* Pass the query to a given callback.
*
* @param callable $callback
* @return $this|mixed
*/
public function tap($callback)
{
return $this->when(true, $callback);
}
}

View File

@@ -3,6 +3,7 @@
namespace Illuminate\Database\Concerns;
use Closure;
use RuntimeException;
use Throwable;
trait ManagesTransactions
@@ -45,6 +46,10 @@ trait ManagesTransactions
}
$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
@@ -78,6 +83,10 @@ trait ManagesTransactions
$this->transactions > 1) {
$this->transactions--;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
throw $e;
}
@@ -107,6 +116,10 @@ trait ManagesTransactions
$this->transactions++;
optional($this->transactionsManager)->begin(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('beganTransaction');
}
@@ -180,6 +193,10 @@ trait ManagesTransactions
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions == 0) {
optional($this->transactionsManager)->commit($this->getName());
}
$this->fireConnectionEvent('committed');
}
@@ -241,6 +258,10 @@ trait ManagesTransactions
$this->transactions = $toLevel;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
$this->fireConnectionEvent('rollingBack');
}
@@ -275,6 +296,10 @@ trait ManagesTransactions
{
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
optional($this->transactionsManager)->rollback(
$this->getName(), $this->transactions
);
}
throw $e;
@@ -289,4 +314,21 @@ trait ManagesTransactions
{
return $this->transactions;
}
/**
* Execute the callback after a transaction commits.
*
* @param callable $callback
* @return void
*
* @throws \RuntimeException
*/
public function afterCommit($callback)
{
if ($this->transactionsManager) {
return $this->transactionsManager->addCallback($callback);
}
throw new RuntimeException('Transactions Manager has not been set.');
}
}

199
vendor/laravel/framework/src/Illuminate/Database/Connection.php vendored Normal file → Executable file
View File

@@ -5,6 +5,7 @@ namespace Illuminate\Database;
use Closure;
use DateTimeInterface;
use Doctrine\DBAL\Connection as DoctrineConnection;
use Doctrine\DBAL\Types\Type;
use Exception;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\QueryExecuted;
@@ -21,6 +22,7 @@ use Illuminate\Support\Arr;
use LogicException;
use PDO;
use PDOStatement;
use RuntimeException;
class Connection implements ConnectionInterface
{
@@ -49,6 +51,13 @@ class Connection implements ConnectionInterface
*/
protected $database;
/**
* The type of the connection.
*
* @var string|null
*/
protected $readWriteType;
/**
* The table prefix for the connection.
*
@@ -112,13 +121,27 @@ class Connection implements ConnectionInterface
*/
protected $transactions = 0;
/**
* The transaction manager instance.
*
* @var \Illuminate\Database\DatabaseTransactionsManager
*/
protected $transactionsManager;
/**
* Indicates if changes have been made to the database.
*
* @var int
* @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.
*
@@ -140,6 +163,13 @@ class Connection implements ConnectionInterface
*/
protected $pretending = false;
/**
* All of the callbacks that should be invoked before a query is executed.
*
* @var array
*/
protected $beforeExecutingCallbacks = [];
/**
* The instance of Doctrine connection.
*
@@ -147,6 +177,13 @@ class Connection implements ConnectionInterface
*/
protected $doctrineConnection;
/**
* Type mappings that should be registered with new Doctrine connections.
*
* @var array
*/
protected $doctrineTypeMappings = [];
/**
* The connection resolvers.
*
@@ -620,6 +657,10 @@ class Connection implements ConnectionInterface
*/
protected function run($query, $bindings, Closure $callback)
{
foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
$beforeExecutingCallback($query, $bindings, $this);
}
$this->reconnectIfMissingConnection();
$start = microtime(true);
@@ -661,7 +702,7 @@ class Connection implements ConnectionInterface
// run the SQL against the PDO connection. Then we can calculate the time it
// took to execute and log the query SQL, bindings and time in our memory.
try {
$result = $callback($query, $bindings);
return $callback($query, $bindings);
}
// If an exception occurs when attempting to run a query, we'll format the error
@@ -672,8 +713,6 @@ class Connection implements ConnectionInterface
$query, $this->prepareBindings($bindings), $e
);
}
return $result;
}
/**
@@ -786,6 +825,21 @@ class Connection implements ConnectionInterface
public function disconnect()
{
$this->setPdo(null)->setReadPdo(null);
$this->doctrineConnection = null;
}
/**
* Register a hook to be run just before a database query is executed.
*
* @param \Closure $callback
* @return $this
*/
public function beforeExecuting(Closure $callback)
{
$this->beforeExecutingCallbacks[] = $callback;
return $this;
}
/**
@@ -847,6 +901,16 @@ class Connection implements ConnectionInterface
return new Expression($value);
}
/**
* Determine if the database connection has modified any database records.
*
* @return bool
*/
public function hasModifiedRecords()
{
return $this->recordsModified;
}
/**
* Indicate if any records have been modified.
*
@@ -860,6 +924,42 @@ class Connection implements ConnectionInterface
}
}
/**
* 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?
*
@@ -891,7 +991,13 @@ class Connection implements ConnectionInterface
*/
public function getDoctrineSchemaManager()
{
return $this->getDoctrineDriver()->getSchemaManager($this->getDoctrineConnection());
$connection = $this->getDoctrineConnection();
// Doctrine v2 expects one parameter while v3 expects two. 2nd will be ignored on v2...
return $this->getDoctrineDriver()->getSchemaManager(
$connection,
$connection->getDatabasePlatform()
);
}
/**
@@ -907,14 +1013,46 @@ class Connection implements ConnectionInterface
$this->doctrineConnection = new DoctrineConnection(array_filter([
'pdo' => $this->getPdo(),
'dbname' => $this->getDatabaseName(),
'driver' => $driver->getName(),
'driver' => method_exists($driver, 'getName') ? $driver->getName() : null,
'serverVersion' => $this->getConfig('server_version'),
]), $driver);
foreach ($this->doctrineTypeMappings as $name => $type) {
$this->doctrineConnection
->getDatabasePlatform()
->registerDoctrineTypeMapping($type, $name);
}
}
return $this->doctrineConnection;
}
/**
* Register a custom Doctrine mapping type.
*
* @param string $class
* @param string $name
* @param string $type
* @return void
*
* @throws \Doctrine\DBAL\DBALException
* @throws \RuntimeException
*/
public function registerDoctrineType(string $class, string $name, string $type): void
{
if (! $this->isDoctrineAvailable()) {
throw new RuntimeException(
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
);
}
if (! Type::hasType($name)) {
Type::addType($name, $class);
}
$this->doctrineTypeMappings[$name] = $type;
}
/**
* Get the current PDO connection.
*
@@ -950,7 +1088,8 @@ class Connection implements ConnectionInterface
return $this->getPdo();
}
if ($this->recordsModified && $this->getConfig('sticky')) {
if ($this->readOnWriteConnection ||
($this->recordsModified && $this->getConfig('sticky'))) {
return $this->getPdo();
}
@@ -1022,6 +1161,16 @@ class Connection implements ConnectionInterface
return $this->getConfig('name');
}
/**
* Get the database connection full name.
*
* @return string|null
*/
public function getNameWithReadWriteType()
{
return $this->getName().($this->readWriteType ? '::'.$this->readWriteType : '');
}
/**
* Get an option from the configuration options.
*
@@ -1145,6 +1294,29 @@ class Connection implements ConnectionInterface
$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".
*
@@ -1228,6 +1400,19 @@ class Connection implements ConnectionInterface
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.
*

View File

@@ -160,4 +160,11 @@ interface ConnectionInterface
* @return array
*/
public function pretend(Closure $callback);
/**
* Get the name of the connected database.
*
* @return string
*/
public function getDatabaseName();
}

View File

View File

View File

View File

View File

View File

View File

@@ -33,6 +33,8 @@ class PostgresConnector extends Connector implements ConnectorInterface
$this->getDsn($config), $config, $this->getOptions($config)
);
$this->configureIsolationLevel($connection, $config);
$this->configureEncoding($connection, $config);
// Next, we will check to see if a timezone has been specified in this config
@@ -52,6 +54,20 @@ class PostgresConnector extends Connector implements ConnectorInterface
return $connection;
}
/**
* Set the connection transaction isolation level.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureIsolationLevel($connection, array $config)
{
if (isset($config['isolation_level'])) {
$connection->prepare("set session characteristics as transaction isolation level {$config['isolation_level']}")->execute();
}
}
/**
* Set the connection character set and collation.
*
@@ -146,7 +162,7 @@ class PostgresConnector extends Connector implements ConnectorInterface
$host = isset($host) ? "host={$host};" : '';
$dsn = "pgsql:{$host}dbname={$database}";
$dsn = "pgsql:{$host}dbname='{$database}'";
// If a port was specified, we will add it to this Postgres DSN connections
// format. Once we have done that we are ready to return this connection

View File

View File

View File

@@ -3,6 +3,7 @@
namespace Illuminate\Database\Console\Factories;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
use Symfony\Component\Console\Input\InputOption;
class FactoryMakeCommand extends GeneratorCommand
@@ -59,19 +60,30 @@ class FactoryMakeCommand extends GeneratorCommand
*/
protected function buildClass($name)
{
$factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name)));
$namespaceModel = $this->option('model')
? $this->qualifyClass($this->option('model'))
: trim($this->rootNamespace(), '\\').'\\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(
@@ -87,11 +99,34 @@ class FactoryMakeCommand extends GeneratorCommand
*/
protected function getPath($name)
{
$name = str_replace(
['\\', '/'], '', $this->argument('name')
);
$name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '')->finish('Factory');
return $this->laravel->databasePath()."/factories/{$name}.php";
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';
}
/**

View File

@@ -1,12 +1,20 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
namespace {{ factoryNamespace }};
use Faker\Generator as Faker;
use {{ namespacedModel }};
use Illuminate\Database\Eloquent\Factories\Factory;
$factory->define({{ model }}::class, function (Faker $faker) {
return [
//
];
});
class {{ factory }}Factory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}

View File

View File

@@ -4,6 +4,8 @@ namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\DatabaseRefreshed;
use Symfony\Component\Console\Input\InputOption;
class FreshCommand extends Command
@@ -48,10 +50,17 @@ class FreshCommand extends Command
'--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);
}
@@ -79,7 +88,7 @@ class FreshCommand extends Command
{
$this->call('db:seed', array_filter([
'--database' => $database,
'--class' => $this->option('seeder') ?: 'DatabaseSeeder',
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
'--force' => true,
]));
}
@@ -98,6 +107,7 @@ class FreshCommand extends Command
['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'],

View File

@@ -3,7 +3,10 @@
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
{
@@ -18,8 +21,10 @@ class MigrateCommand extends BaseCommand
{--force : Force the operation to run when in production}
{--path=* : The path(s) to the migrations files to be executed}
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
{--schema-path= : The path to a schema dump file}
{--pretend : Dump the SQL queries that would be run}
{--seed : Indicates if the seed task should be re-run}
{--seeder= : The class name of the root seeder}
{--step : Force the migrations to be run so they can be rolled back individually}';
/**
@@ -36,17 +41,26 @@ class MigrateCommand extends BaseCommand
*/
protected $migrator;
/**
* The event dispatcher instance.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $dispatcher;
/**
* Create a new migration command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public function __construct(Migrator $migrator)
public function __construct(Migrator $migrator, Dispatcher $dispatcher)
{
parent::__construct();
$this->migrator = $migrator;
$this->dispatcher = $dispatcher;
}
/**
@@ -76,7 +90,10 @@ class MigrateCommand extends BaseCommand
// 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]);
$this->call('db:seed', [
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
'--force' => true,
]);
}
});
@@ -95,5 +112,70 @@ class MigrateCommand extends BaseCommand
'--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');
}
}

View File

@@ -132,14 +132,4 @@ class MigrateMakeCommand extends BaseCommand
return parent::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');
}
}

View File

@@ -4,6 +4,8 @@ namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events\DatabaseRefreshed;
use Symfony\Component\Console\Input\InputOption;
class RefreshCommand extends Command
@@ -63,6 +65,12 @@ class RefreshCommand extends Command
'--force' => true,
]));
if ($this->laravel->bound(Dispatcher::class)) {
$this->laravel[Dispatcher::class]->dispatch(
new DatabaseRefreshed
);
}
if ($this->needsSeeding()) {
$this->runSeeder($database);
}
@@ -126,7 +134,7 @@ class RefreshCommand extends Command
{
$this->call('db:seed', array_filter([
'--database' => $database,
'--class' => $this->option('seeder') ?: 'DatabaseSeeder',
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
'--force' => true,
]));
}

View File

@@ -67,8 +67,6 @@ class ResetCommand extends BaseCommand
$this->getMigrationPaths(), $this->option('pretend')
);
});
return 0;
}
/**

View File

@@ -6,6 +6,7 @@ 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
@@ -81,9 +82,20 @@ class SeedCommand extends Command
*/
protected function getSeeder()
{
$class = $this->laravel->make($this->input->getOption('class'));
$class = $this->input->getArgument('class') ?? $this->input->getOption('class');
return $class->setContainer($this->laravel)->setCommand($this);
if (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);
}
/**
@@ -98,6 +110,18 @@ class SeedCommand extends Command
return $database ?: $this->laravel['config']['database.default'];
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return [
['class', InputArgument::OPTIONAL, 'The class name of the root seeder', null],
];
}
/**
* Get the console command options.
*
@@ -106,10 +130,8 @@ class SeedCommand extends Command
protected function getOptions()
{
return [
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'DatabaseSeeder'],
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'],
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
];
}

View File

@@ -3,8 +3,6 @@
namespace Illuminate\Database\Console\Seeds;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Composer;
class SeederMakeCommand extends GeneratorCommand
{
@@ -29,27 +27,6 @@ class SeederMakeCommand extends GeneratorCommand
*/
protected $type = 'Seeder';
/**
* The Composer instance.
*
* @var \Illuminate\Support\Composer
*/
protected $composer;
/**
* Create a new command instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param \Illuminate\Support\Composer $composer
* @return void
*/
public function __construct(Filesystem $files, Composer $composer)
{
parent::__construct($files);
$this->composer = $composer;
}
/**
* Execute the console command.
*
@@ -58,8 +35,6 @@ class SeederMakeCommand extends GeneratorCommand
public function handle()
{
parent::handle();
$this->composer->dumpAutoloads();
}
/**
@@ -80,7 +55,7 @@ class SeederMakeCommand extends GeneratorCommand
*/
protected function resolveStubPath($stub)
{
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
? $customPath
: __DIR__.$stub;
}
@@ -93,7 +68,11 @@ class SeederMakeCommand extends GeneratorCommand
*/
protected function getPath($name)
{
return $this->laravel->databasePath().'/seeds/'.$name.'.php';
if (is_dir($this->laravel->databasePath().'/seeds')) {
return $this->laravel->databasePath().'/seeds/'.$name.'.php';
} else {
return $this->laravel->databasePath().'/seeders/'.$name.'.php';
}
}
/**

View File

@@ -1,5 +1,7 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class {{ class }} extends Seeder

85
vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php vendored Normal file → Executable file
View File

@@ -2,12 +2,14 @@
namespace Illuminate\Database;
use Doctrine\DBAL\Types\Type;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Support\Arr;
use Illuminate\Support\ConfigurationUrlParser;
use Illuminate\Support\Str;
use InvalidArgumentException;
use PDO;
use RuntimeException;
/**
* @mixin \Illuminate\Database\Connection
@@ -49,6 +51,13 @@ class DatabaseManager implements ConnectionResolverInterface
*/
protected $reconnector;
/**
* The custom Doctrine column types.
*
* @var array
*/
protected $doctrineTypes = [];
/**
* Create a new database manager instance.
*
@@ -62,7 +71,7 @@ class DatabaseManager implements ConnectionResolverInterface
$this->factory = $factory;
$this->reconnector = function ($connection) {
$this->reconnect($connection->getName());
$this->reconnect($connection->getNameWithReadWriteType());
};
}
@@ -165,7 +174,7 @@ class DatabaseManager implements ConnectionResolverInterface
*/
protected function configure(Connection $connection, $type)
{
$connection = $this->setPdoForType($connection, $type);
$connection = $this->setPdoForType($connection, $type)->setReadWriteType($type);
// First we'll set the fetch mode and a few other dependencies of the database
// connection. This method basically just configures and prepares it to get
@@ -174,11 +183,17 @@ class DatabaseManager implements ConnectionResolverInterface
$connection->setEventDispatcher($this->app['events']);
}
if ($this->app->bound('db.transactions')) {
$connection->setTransactionManager($this->app['db.transactions']);
}
// Here we'll set a reconnector callback. This reconnector can be any callable
// so we will set a Closure to reconnect from this manager with the name of
// the connection, which will allow us to reconnect from the connections.
$connection->setReconnector($this->reconnector);
$this->registerConfiguredDoctrineTypes($connection);
return $connection;
}
@@ -200,6 +215,49 @@ class DatabaseManager implements ConnectionResolverInterface
return $connection;
}
/**
* Register custom Doctrine types with the connection.
*
* @param \Illuminate\Database\Connection $connection
* @return void
*/
protected function registerConfiguredDoctrineTypes(Connection $connection): void
{
foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) {
$this->registerDoctrineType($class, $name, $name);
}
foreach ($this->doctrineTypes as $name => [$type, $class]) {
$connection->registerDoctrineType($class, $name, $type);
}
}
/**
* Register a custom Doctrine type.
*
* @param string $class
* @param string $name
* @param string $type
* @return void
*
* @throws \Doctrine\DBAL\DBALException
* @throws \RuntimeException
*/
public function registerDoctrineType(string $class, string $name, string $type): void
{
if (! class_exists('Doctrine\DBAL\Connection')) {
throw new RuntimeException(
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
);
}
if (! Type::hasType($name)) {
Type::addType($name, $class);
}
$this->doctrineTypes[$name] = [$type, $class];
}
/**
* Disconnect from the given database and remove from local cache.
*
@@ -271,11 +329,15 @@ class DatabaseManager implements ConnectionResolverInterface
*/
protected function refreshPdoConnections($name)
{
$fresh = $this->makeConnection($name);
[$database, $type] = $this->parseConnectionName($name);
$fresh = $this->configure(
$this->makeConnection($database), $type
);
return $this->connections[$name]
->setPdo($fresh->getRawPdo())
->setReadPdo($fresh->getRawReadPdo());
->setPdo($fresh->getRawPdo())
->setReadPdo($fresh->getRawReadPdo());
}
/**
@@ -355,6 +417,19 @@ class DatabaseManager implements ConnectionResolverInterface
$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.
*

View File

@@ -6,7 +6,6 @@ use Faker\Factory as FakerFactory;
use Faker\Generator as FakerGenerator;
use Illuminate\Contracts\Queue\EntityResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\QueueEntityResolver;
use Illuminate\Support\ServiceProvider;
@@ -42,9 +41,7 @@ class DatabaseServiceProvider extends ServiceProvider
Model::clearBootedModels();
$this->registerConnectionServices();
$this->registerEloquentFactory();
$this->registerQueueableEntityResolver();
}
@@ -72,6 +69,10 @@ class DatabaseServiceProvider extends ServiceProvider
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
$this->app->singleton('db.transactions', function ($app) {
return new DatabaseTransactionsManager;
});
}
/**
@@ -92,12 +93,6 @@ class DatabaseServiceProvider extends ServiceProvider
return static::$fakers[$locale];
});
$this->app->singleton(EloquentFactory::class, function ($app) {
return EloquentFactory::construct(
$app->make(FakerGenerator::class), $this->app->databasePath('factories')
);
});
}
/**

View File

@@ -16,7 +16,7 @@ trait DetectsConcurrencyErrors
*/
protected function causedByConcurrencyError(Throwable $e)
{
if ($e instanceof PDOException && $e->getCode() === '40001') {
if ($e instanceof PDOException && ($e->getCode() === 40001 || $e->getCode() === '40001')) {
return true;
}

View File

@@ -50,6 +50,13 @@ trait DetectsLostConnections
'SSL: Connection timed out',
'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
'Temporary failure in name resolution',
'SSL: Broken pipe',
'SQLSTATE[08S01]: Communication link failure',
'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',
'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',
'SQLSTATE[08006] [7] could not translate host name',
'TCP Provider: Error code 0x274C',
]);
}
}

279
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php vendored Normal file → Executable file
View File

@@ -10,6 +10,7 @@ use Illuminate\Database\Concerns\BuildsQueries;
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;
@@ -24,7 +25,10 @@ use ReflectionMethod;
*/
class Builder
{
use BuildsQueries, Concerns\QueriesRelationships, ForwardsCalls;
use Concerns\QueriesRelationships, ForwardsCalls;
use BuildsQueries {
sole as baseSole;
}
/**
* The base query builder instance.
@@ -68,14 +72,42 @@ class Builder
*/
protected $onDelete;
/**
* The properties that should be returned from query builder.
*
* @var string[]
*/
protected $propertyPassthru = [
'from',
];
/**
* The methods that should be returned from query builder.
*
* @var array
* @var string[]
*/
protected $passthru = [
'insert', 'insertOrIgnore', 'insertGetId', 'insertUsing', 'getBindings', 'toSql', 'dump', 'dd',
'exists', 'doesntExist', 'count', 'min', 'max', 'avg', 'average', 'sum', 'getConnection', 'raw', 'getGrammar',
'aggregate',
'average',
'avg',
'count',
'dd',
'doesntExist',
'dump',
'exists',
'explain',
'getBindings',
'getConnection',
'getGrammar',
'insert',
'insertGetId',
'insertOrIgnore',
'insertUsing',
'max',
'min',
'raw',
'sum',
'toSql',
];
/**
@@ -188,6 +220,10 @@ class Builder
*/
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);
@@ -209,6 +245,10 @@ class Builder
*/
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);
@@ -251,7 +291,7 @@ class Builder
* @param mixed $operator
* @param mixed $value
* @param string $boolean
* @return \Illuminate\Database\Eloquent\Model|static
* @return \Illuminate\Database\Eloquent\Model|static|null
*/
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
{
@@ -319,8 +359,14 @@ class Builder
{
$instance = $this->newModelInstance();
return $instance->newCollection(array_map(function ($item) use ($instance) {
return $instance->newFromBuilder($item);
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));
}
@@ -429,7 +475,7 @@ class Builder
return $instance;
}
return $this->newModelInstance($attributes + $values);
return $this->newModelInstance(array_merge($attributes, $values));
}
/**
@@ -439,13 +485,13 @@ class Builder
* @param array $values
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function firstOrCreate(array $attributes, array $values = [])
public function firstOrCreate(array $attributes = [], array $values = [])
{
if (! is_null($instance = $this->where($attributes)->first())) {
return $instance;
}
return tap($this->newModelInstance($attributes + $values), function ($instance) {
return tap($this->newModelInstance(array_merge($attributes, $values)), function ($instance) {
$instance->save();
});
}
@@ -503,6 +549,24 @@ class Builder
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.
*
@@ -516,6 +580,19 @@ class Builder
}
}
/**
* 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.
*
@@ -764,6 +841,49 @@ class Builder
]);
}
/**
* 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)
{
if (empty($this->query->orders) && empty($this->query->unionOrders)) {
$this->enforceOrderBy();
}
if ($shouldReverse) {
$this->query->orders = collect($this->query->orders)->map(function ($order) {
$order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
return $order;
})->toArray();
}
if ($this->query->unionOrders) {
return collect($this->query->unionOrders);
}
return collect($this->query->orders);
}
/**
* Save a new model and return the instance.
*
@@ -791,7 +911,7 @@ class Builder
}
/**
* Update a record in the database.
* Update records in the database.
*
* @param array $values
* @return int
@@ -801,6 +921,35 @@ class Builder
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.
*
@@ -855,7 +1004,7 @@ class Builder
$qualifiedColumn = end($segments).'.'.$column;
$values[$qualifiedColumn] = $values[$column];
$values[$qualifiedColumn] = Arr::get($values, $qualifiedColumn, $values[$column]);
unset($values[$column]);
@@ -863,7 +1012,58 @@ class Builder
}
/**
* Delete a record from the database.
* 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
*/
@@ -931,7 +1131,9 @@ class Builder
// 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, (array) $parameters);
$builder = $builder->callNamedScope(
$scope, Arr::wrap($parameters)
);
}
return $builder;
@@ -1084,12 +1286,17 @@ class Builder
/**
* Set the relationships that should be eager loaded.
*
* @param mixed $relations
* @param string|array $relations
* @param string|\Closure|null $callback
* @return $this
*/
public function with($relations)
public function with($relations, $callback = null)
{
$eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);
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);
@@ -1111,6 +1318,19 @@ class Builder
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.
*
@@ -1322,6 +1542,17 @@ class Builder
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.
*
@@ -1380,6 +1611,10 @@ class Builder
return new HigherOrderBuilderProxy($this, $key);
}
if (in_array($key, $this->propertyPassthru)) {
return $this->toBase()->{$key};
}
throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance.");
}
@@ -1483,6 +1718,16 @@ class Builder
}
}
/**
* Clone the Eloquent query builder.
*
* @return static
*/
public function clone()
{
return clone $this;
}
/**
* Force a clone of the underlying query builder when cloning.
*

View File

@@ -64,12 +64,14 @@ class Collection extends BaseCollection implements QueueableCollection
}
/**
* Load a set of relationship counts onto the collection.
* 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 loadCount($relations)
public function loadAggregate($relations, $column, $function = null)
{
if ($this->isEmpty()) {
return $this;
@@ -78,7 +80,7 @@ class Collection extends BaseCollection implements QueueableCollection
$models = $this->first()->newModelQuery()
->whereKey($this->modelKeys())
->select($this->first()->getKeyName())
->withCount(...func_get_args())
->withAggregate($relations, $column, $function)
->get()
->keyBy($this->first()->getKeyName());
@@ -90,12 +92,84 @@ class Collection extends BaseCollection implements QueueableCollection
$this->each(function ($model) use ($models, $attributes) {
$extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes);
$model->forceFill($extraAttributes)->syncOriginalAttributes($attributes);
$model->forceFill($extraAttributes)
->syncOriginalAttributes($attributes)
->mergeCasts($models->get($model->getKey())->getCasts());
});
return $this;
}
/**
* Load a set of relationship counts onto the collection.
*
* @param array|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.
*
@@ -160,7 +234,7 @@ class Collection extends BaseCollection implements QueueableCollection
return;
}
$models = $models->pluck($name);
$models = $models->pluck($name)->whereNotNull();
if ($models->first() instanceof BaseCollection) {
$models = $models->collapse();
@@ -317,9 +391,11 @@ class Collection extends BaseCollection implements QueueableCollection
->get()
->getDictionary();
return $this->map(function ($model) use ($freshModels) {
return $model->exists && isset($freshModels[$model->getKey()])
? $freshModels[$model->getKey()] : null;
return $this->filter(function ($model) use ($freshModels) {
return $model->exists && isset($freshModels[$model->getKey()]);
})
->map(function ($model) use ($freshModels) {
return $freshModels[$model->getKey()];
});
}
@@ -618,7 +694,7 @@ class Collection extends BaseCollection implements QueueableCollection
} elseif (count($relations) === 1) {
return reset($relations);
} else {
return array_intersect(...$relations);
return array_intersect(...array_values($relations));
}
}

View File

@@ -9,14 +9,14 @@ trait GuardsAttributes
/**
* The attributes that are mass assignable.
*
* @var array
* @var string[]
*/
protected $fillable = [];
/**
* The attributes that aren't mass assignable.
*
* @var array
* @var string[]|bool
*/
protected $guarded = ['*'];
@@ -77,7 +77,9 @@ trait GuardsAttributes
*/
public function getGuarded()
{
return $this->guarded;
return $this->guarded === false
? []
: $this->guarded;
}
/**
@@ -128,7 +130,7 @@ trait GuardsAttributes
}
/**
* Determine if current state is "unguarded".
* Determine if the current state is "unguarded".
*
* @return bool
*/
@@ -215,9 +217,14 @@ trait GuardsAttributes
protected function isGuardableColumn($key)
{
if (! isset(static::$guardableColumns[get_class($this)])) {
static::$guardableColumns[get_class($this)] = $this->getConnection()
$columns = $this->getConnection()
->getSchemaBuilder()
->getColumnListing($this->getTable());
if (empty($columns)) {
return true;
}
static::$guardableColumns[get_class($this)] = $columns;
}
return in_array($key, static::$guardableColumns[get_class($this)]);

View File

@@ -147,7 +147,7 @@ trait HasEvents
* Register a model event with the dispatcher.
*
* @param string $event
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
protected static function registerModelEvent($event, $callback)
@@ -230,7 +230,7 @@ trait HasEvents
/**
* Register a retrieved model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function retrieved($callback)
@@ -241,7 +241,7 @@ trait HasEvents
/**
* Register a saving model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function saving($callback)
@@ -252,7 +252,7 @@ trait HasEvents
/**
* Register a saved model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function saved($callback)
@@ -263,7 +263,7 @@ trait HasEvents
/**
* Register an updating model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function updating($callback)
@@ -274,7 +274,7 @@ trait HasEvents
/**
* Register an updated model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function updated($callback)
@@ -285,7 +285,7 @@ trait HasEvents
/**
* Register a creating model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function creating($callback)
@@ -296,7 +296,7 @@ trait HasEvents
/**
* Register a created model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function created($callback)
@@ -307,7 +307,7 @@ trait HasEvents
/**
* Register a replicating model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function replicating($callback)
@@ -318,7 +318,7 @@ trait HasEvents
/**
* Register a deleting model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function deleting($callback)
@@ -329,7 +329,7 @@ trait HasEvents
/**
* Register a deleted model event with the dispatcher.
*
* @param \Closure|string $callback
* @param \Illuminate\Events\QueuedClosure|\Closure|string $callback
* @return void
*/
public static function deleted($callback)

View File

@@ -3,6 +3,7 @@
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;
@@ -39,7 +40,7 @@ trait HasRelationships
/**
* The many to many relationship methods.
*
* @var array
* @var string[]
*/
public static $manyMethods = [
'belongsToMany', 'morphToMany', 'morphedByMany',
@@ -256,7 +257,7 @@ trait HasRelationships
// If the type value is null it is probably safe to assume we're eager loading
// the relationship. In this case we'll just pass in a dummy query where we
// need to remove any eager loads that may already be defined on a model.
return is_null($class = $this->{$type}) || $class === ''
return is_null($class = $this->getAttributeFromArray($type)) || $class === ''
? $this->morphEagerTo($name, $type, $id, $ownerKey)
: $this->morphInstanceTo($class, $name, $type, $id, $ownerKey);
}
@@ -731,6 +732,10 @@ trait HasRelationships
return array_search(static::class, $morphMap, true);
}
if (Relation::requiresMorphMap()) {
throw new ClassMorphViolationException($this);
}
return static::class;
}

View File

@@ -34,7 +34,7 @@ trait HasTimestamps
*
* @return void
*/
protected function updateTimestamps()
public function updateTimestamps()
{
$time = $this->freshTimestamp();
@@ -130,7 +130,7 @@ trait HasTimestamps
/**
* Get the fully qualified "created at" column.
*
* @return string
* @return string|null
*/
public function getQualifiedCreatedAtColumn()
{
@@ -140,7 +140,7 @@ trait HasTimestamps
/**
* Get the fully qualified "updated at" column.
*
* @return string
* @return string|null
*/
public function getQualifiedUpdatedAtColumn()
{

View File

@@ -94,9 +94,7 @@ trait HidesAttributes
*/
public function makeVisibleIf($condition, $attributes)
{
$condition = $condition instanceof Closure ? $condition($this) : $condition;
return $condition ? $this->makeVisible($attributes) : $this;
return value($condition, $this) ? $this->makeVisible($attributes) : $this;
}
/**
@@ -123,8 +121,6 @@ trait HidesAttributes
*/
public function makeHiddenIf($condition, $attributes)
{
$condition = $condition instanceof Closure ? $condition($this) : $condition;
return value($condition) ? $this->makeHidden($attributes) : $this;
return value($condition, $this) ? $this->makeHidden($attributes) : $this;
}
}

View File

@@ -2,14 +2,16 @@
namespace Illuminate\Database\Eloquent\Concerns;
use BadMethodCallException;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\RelationNotFoundException;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Str;
use RuntimeException;
trait QueriesRelationships
{
@@ -36,7 +38,7 @@ trait QueriesRelationships
}
if ($relation instanceof MorphTo) {
throw new RuntimeException('Please use whereHasMorph() for MorphTo relationships.');
return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback);
}
// If we only need to check for the existence of the relation, then we can optimize
@@ -189,7 +191,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query.
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param string $operator
* @param int $count
@@ -199,7 +201,9 @@ trait QueriesRelationships
*/
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
$relation = $this->getRelationWithoutConstraints($relation);
if (is_string($relation)) {
$relation = $this->getRelationWithoutConstraints($relation);
}
$types = (array) $types;
@@ -222,7 +226,7 @@ trait QueriesRelationships
};
}
$query->where($this->query->from.'.'.$relation->getMorphType(), '=', (new $type)->getMorphClass())
$query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass())
->whereHas($belongsTo, $callback, $operator, $count);
});
}
@@ -254,7 +258,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query with an "or".
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param string $operator
* @param int $count
@@ -268,7 +272,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query.
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param string $boolean
* @param \Closure|null $callback
@@ -282,7 +286,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query with an "or".
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @return \Illuminate\Database\Eloquent\Builder|static
*/
@@ -294,7 +298,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param \Closure|null $callback
* @param string $operator
@@ -309,7 +313,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param \Closure|null $callback
* @param string $operator
@@ -324,7 +328,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
@@ -337,7 +341,7 @@ trait QueriesRelationships
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
*
* @param string $relation
* @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
* @param string|array $types
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
@@ -348,12 +352,171 @@ trait QueriesRelationships
}
/**
* Add subselect queries to count the relations.
* 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 a "belongs to" relationship where clause to the query.
*
* @param \Illuminate\Database\Eloquent\Model $related
* @param string $relationship
* @param string $boolean
* @return $this
*
* @throws \RuntimeException
*/
public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and')
{
if ($relationshipName === null) {
$relationshipName = Str::camel(class_basename($related));
}
try {
$relationship = $this->model->{$relationshipName}();
} catch (BadMethodCallException $exception) {
throw RelationNotFoundException::make($this->model, $relationshipName);
}
if (! $relationship instanceof BelongsTo) {
throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class);
}
$this->where(
$relationship->getQualifiedForeignKeyName(),
'=',
$related->getAttributeValue($relationship->getOwnerKeyName()),
$boolean,
);
return $this;
}
/**
* Add an "BelongsTo" relationship with an "or where" clause to the query.
*
* @param \Illuminate\Database\Eloquent\Model $related
* @param string $relationship
* @return $this
*
* @throws \RuntimeException
*/
public function orWhereBelongsTo($related, $relationshipName = null)
{
return $this->whereBelongsTo($related, $relationshipName, 'or');
}
/**
* Add subselect queries to include an aggregate value for a relationship.
*
* @param mixed $relations
* @param string $column
* @param string $function
* @return $this
*/
public function withCount($relations)
public function withAggregate($relations, $column, $function = null)
{
if (empty($relations)) {
return $this;
@@ -363,12 +526,12 @@ trait QueriesRelationships
$this->query->select([$this->query->from.'.*']);
}
$relations = is_array($relations) ? $relations : func_get_args();
$relations = is_array($relations) ? $relations : [$relations];
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
// First we will determine if the name has been aliased using an "as" clause on the name
// and if it has we will extract the actual relationship name and the desired name of
// the resulting column. This allows multiple counts on the same relationship name.
// the resulting column. This allows multiple aggregates on the same relationships.
$segments = explode(' ', $name);
unset($alias);
@@ -379,38 +542,135 @@ trait QueriesRelationships
$relation = $this->getRelationWithoutConstraints($name);
// Here we will get the relationship count query and prepare to add it to the main query
if ($function) {
$hashedColumn = $this->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
// count query. We will normalize the relation name then append _count as the name.
$query = $relation->getRelationExistenceCountQuery(
$relation->getRelated()->newQuery(), $this
);
// sub-query. We'll format this relationship name and append this column if needed.
$query = $relation->getRelationExistenceQuery(
$relation->getRelated()->newQuery(), $this, new Expression($expression)
)->setBindings([], 'select');
$query->callScope($constraints);
$query = $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 add the proper result column alias to the query and run the subselect
// statement against the query builder. Then we will return the builder instance back
// to the developer for further constraint chaining that needs to take place on it.
$column = $alias ?? Str::snake($name.'_count');
// 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")
);
$this->selectSub($query, $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.
*

View File

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

View File

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

View File

@@ -3,13 +3,15 @@
namespace Illuminate\Database\Eloquent;
use ArrayAccess;
use Exception;
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\CanBeEscapedWhenCastToString;
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;
@@ -19,8 +21,9 @@ 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, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
use Concerns\HasAttributes,
Concerns\HasEvents,
@@ -80,6 +83,13 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
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.
*
@@ -101,6 +111,13 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public $wasRecentlyCreated = false;
/**
* Indicates that the object's string representation should be escaped when __toString is invoked.
*
* @var bool
*/
protected $escapeWhenCastingToString = false;
/**
* The connection resolver instance.
*
@@ -143,6 +160,27 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
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.
*
@@ -332,6 +370,47 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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|null $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.
*
@@ -345,8 +424,6 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// 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.
@@ -392,16 +469,16 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
}
/**
* Remove the table name from a given key.
* Qualify the given columns with the model's table.
*
* @param string $key
* @return string
*
* @deprecated This method is deprecated and will be removed in a future Laravel version.
* @param array $columns
* @return array
*/
protected function removeTableFromKey($key)
public function qualifyColumns($columns)
{
return $key;
return collect($columns)->map(function ($column) {
return $this->qualifyColumn($column);
})->all();
}
/**
@@ -531,6 +608,10 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function loadMorph($relation, $relations)
{
if (! $this->{$relation}) {
return $this;
}
$className = get_class($this->{$relation});
$this->{$relation}->load($relations[$className] ?? []);
@@ -553,6 +634,21 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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.
*
@@ -563,7 +659,86 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
{
$relations = is_string($relations) ? func_get_args() : $relations;
$this->newCollection([$this])->loadCount($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;
}
@@ -577,11 +752,59 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function loadMorphCount($relation, $relations)
{
$className = get_class($this->{$relation});
return $this->loadMorphAggregate($relation, $relations, '*', 'count');
}
$this->{$relation}->loadCount($relations[$className] ?? []);
/**
* 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');
}
return $this;
/**
* 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');
}
/**
@@ -627,29 +850,23 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
return $query->{$method}($column, $amount, $extra);
}
$this->incrementOrDecrementAttributeValue($column, $amount, $extra, $method);
return $query->where(
$this->getKeyName(), $this->getKey()
)->{$method}($column, $amount, $extra);
}
/**
* Increment the underlying attribute value and sync with original.
*
* @param string $column
* @param float|int $amount
* @param array $extra
* @param string $method
* @return void
*/
protected function incrementOrDecrementAttributeValue($column, $amount, $extra, $method)
{
$this->{$column} = $this->{$column} + ($method === 'increment' ? $amount : $amount * -1);
$this->{$column} = $this->isClassDeviable($column)
? $this->deviateClassCastableAttribute($method, $column, $amount)
: $this->{$column} + ($method === 'increment' ? $amount : $amount * -1);
$this->forceFill($extra);
$this->syncOriginalAttribute($column);
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);
});
}
/**
@@ -668,6 +885,40 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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.
*
@@ -696,6 +947,19 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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.
*
@@ -704,7 +968,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function save(array $options = [])
{
$this->mergeAttributesFromClassCasts();
$this->mergeAttributesFromCachedCasts();
$query = $this->newModelQuery();
@@ -746,7 +1010,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
}
/**
* Save the model to the database using transaction.
* Save the model to the database within a transaction.
*
* @param array $options
* @return bool
@@ -815,13 +1079,36 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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(Builder $query)
protected function setKeysForSaveQuery($query)
{
$query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery());
@@ -835,8 +1122,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
protected function getKeyForSaveQuery()
{
return $this->original[$this->getKeyName()]
?? $this->getKey();
return $this->original[$this->getKeyName()] ?? $this->getKey();
}
/**
@@ -861,7 +1147,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
// 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->getAttributes();
$attributes = $this->getAttributesForInsert();
if ($this->getIncrementing()) {
$this->insertAndSetId($query, $attributes);
@@ -912,10 +1198,9 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public static function destroy($ids)
{
// We'll initialize a count here so we will return the total number of deletes
// for the operation. The developers can then check this number as a boolean
// type value or get this total count of records deleted for logging, etc.
$count = 0;
if ($ids instanceof EloquentCollection) {
$ids = $ids->modelKeys();
}
if ($ids instanceof BaseCollection) {
$ids = $ids->all();
@@ -923,11 +1208,17 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
$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++;
@@ -942,14 +1233,14 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*
* @return bool|null
*
* @throws \Exception
* @throws \LogicException
*/
public function delete()
{
$this->mergeAttributesFromClassCasts();
$this->mergeAttributesFromCachedCasts();
if (is_null($this->getKeyName())) {
throw new Exception('No primary key defined on model.');
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
@@ -978,10 +1269,28 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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 trait is missing.
* This method protects developers from running forceDelete when the trait is missing.
*
* @return bool|null
*/
@@ -1200,6 +1509,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();
@@ -1217,9 +1527,8 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
return;
}
return static::newQueryWithoutScopes()
return $this->setKeysForSelectQuery($this->newQueryWithoutScopes())
->with(is_string($with) ? func_get_args() : $with)
->where($this->getKeyName(), $this->getKey())
->first();
}
@@ -1235,7 +1544,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
}
$this->setRawAttributes(
static::newQueryWithoutScopes()->findOrFail($this->getKey())->attributes
$this->setKeysForSelectQuery($this->newQueryWithoutScopes())->firstOrFail()->attributes
);
$this->load(collect($this->relations)->reject(function ($relation) {
@@ -1568,7 +1877,19 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function resolveRouteBinding($value, $field = null)
{
return $this->where($field ?? $this->getRouteKeyName(), $value)->first();
return $this->resolveRouteBindingQuery($this, $value, $field)->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->resolveRouteBindingQuery($this, $value, $field)->withTrashed()->first();
}
/**
@@ -1580,6 +1901,32 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
* @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\Relations\Relation
*/
protected function resolveChildRouteBindingQuery($childType, $value, $field)
{
$relationship = $this->{Str::plural(Str::camel($childType))}();
@@ -1587,10 +1934,25 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
if ($relationship instanceof HasManyThrough ||
$relationship instanceof BelongsToMany) {
return $relationship->where($relationship->getRelated()->getTable().'.'.$field, $value)->first();
} else {
return $relationship->where($field, $value)->first();
$field = $relationship->getRelated()->getTable().'.'.$field;
}
return $relationship instanceof Model
? $relationship->resolveRouteBindingQuery($relationship, $value, $field)
: $relationship->getRelated()->resolveRouteBindingQuery($relationship, $value, $field);
}
/**
* Retrieve the model for a bound value.
*
* @param \Illuminate\Database\Eloquent\Model|Illuminate\Database\Eloquent\Relations\Relation $query
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function resolveRouteBindingQuery($query, $value, $field = null)
{
return $query->where($field ?? $this->getRouteKeyName(), $value);
}
/**
@@ -1626,6 +1988,36 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
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.
*
@@ -1655,6 +2047,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
* @param mixed $offset
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return ! is_null($this->getAttribute($offset));
@@ -1666,6 +2059,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
* @param mixed $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->getAttribute($offset);
@@ -1678,6 +2072,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->setAttribute($offset, $value);
@@ -1689,6 +2084,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
* @param mixed $offset
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
unset($this->attributes[$offset], $this->relations[$offset]);
@@ -1755,7 +2151,22 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function __toString()
{
return $this->toJson();
return $this->escapeWhenCastingToString
? e($this->toJson())
: $this->toJson();
}
/**
* Indicate that the object's string representation should be escaped when __toString is invoked.
*
* @param bool $escape
* @return $this
*/
public function escapeWhenCastingToString($escape = true)
{
$this->escapeWhenCastingToString = $escape;
return $this;
}
/**
@@ -1765,9 +2176,10 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
*/
public function __sleep()
{
$this->mergeAttributesFromClassCasts();
$this->mergeAttributesFromCachedCasts();
$this->classCastCache = [];
$this->attributeCastCache = [];
return array_keys(get_object_vars($this));
}
@@ -1780,5 +2192,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializab
public function __wakeup()
{
$this->bootIfNotBooted();
$this->initializeTraits();
}
}

View File

@@ -2,10 +2,10 @@
namespace Illuminate\Database\Eloquent;
use Illuminate\Database\RecordsNotFoundException;
use Illuminate\Support\Arr;
use RuntimeException;
class ModelNotFoundException extends RuntimeException
class ModelNotFoundException extends RecordsNotFoundException
{
/**
* Name of the affected Eloquent model.

View File

@@ -25,13 +25,18 @@ class RelationNotFoundException extends RuntimeException
*
* @param object $model
* @param string $relation
* @param string|null $type
* @return static
*/
public static function make($model, $relation)
public static function make($model, $relation, $type = null)
{
$class = get_class($model);
$instance = new static("Call to undefined relationship [{$relation}] on model [{$class}].");
$instance = new static(
is_null($type)
? "Call to undefined relationship [{$relation}] on model [{$class}]."
: "Call to undefined relationship [{$relation}] on model [{$class}] of type [{$type}].",
);
$instance->model = $class;
$instance->relation = $relation;

View File

@@ -5,11 +5,15 @@ 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 SupportsDefaultModels;
use ComparesRelatedModels,
InteractsWithDictionary,
SupportsDefaultModels;
/**
* The child model instance of the relation.
@@ -39,13 +43,6 @@ class BelongsTo extends Relation
*/
protected $relationName;
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new belongs to relationship instance.
*
@@ -179,15 +176,19 @@ class BelongsTo extends Relation
$dictionary = [];
foreach ($results as $result) {
$dictionary[$result->getAttribute($owner)] = $result;
$attribute = $this->getDictionaryKey($result->getAttribute($owner));
$dictionary[$attribute] = $result;
}
// Once we have the dictionary constructed, we can loop through all the parents
// and match back onto their children using these keys of the dictionary and
// the primary key of the children to map them onto the correct instances.
foreach ($models as $model) {
if (isset($dictionary[$model->{$foreign}])) {
$model->setRelation($relation, $dictionary[$model->{$foreign}]);
$attribute = $this->getDictionaryKey($model->{$foreign});
if (isset($dictionary[$attribute])) {
$model->setRelation($relation, $dictionary[$attribute]);
}
}
@@ -197,7 +198,7 @@ class BelongsTo extends Relation
/**
* Associate the model instance to the given parent.
*
* @param \Illuminate\Database\Eloquent\Model|int|string $model
* @param \Illuminate\Database\Eloquent\Model|int|string|null $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function associate($model)
@@ -227,6 +228,16 @@ class BelongsTo extends Relation
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.
*
@@ -267,16 +278,6 @@ class BelongsTo extends Relation
);
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Determine if the related model has an auto-incrementing ID.
*
@@ -329,6 +330,16 @@ class BelongsTo extends Relation
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.
*
@@ -349,6 +360,17 @@ class BelongsTo extends Relation
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.
*

View File

@@ -2,17 +2,21 @@
namespace Illuminate\Database\Eloquent\Relations;
use Closure;
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 Concerns\InteractsWithPivotTable;
use InteractsWithDictionary, InteractsWithPivotTable;
/**
* The intermediate table for the relation.
@@ -126,13 +130,6 @@ class BelongsToMany extends Relation
*/
protected $accessor = 'pivot';
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new belongs to many relationship instance.
*
@@ -177,7 +174,7 @@ class BelongsToMany extends Relation
return $table;
}
if ($model instanceof Pivot) {
if (in_array(AsPivot::class, class_uses_recursive($model))) {
$this->using($table);
}
@@ -211,11 +208,12 @@ class BelongsToMany extends Relation
// 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.
$baseTable = $this->related->getTable();
$key = $baseTable.'.'.$this->relatedKey;
$query->join($this->table, $key, '=', $this->getQualifiedRelatedPivotKeyName());
$query->join(
$this->table,
$this->getQualifiedRelatedKeyName(),
'=',
$this->getQualifiedRelatedPivotKeyName()
);
return $this;
}
@@ -280,9 +278,11 @@ class BelongsToMany extends Relation
// 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.
// parent models. Then we should return these hydrated models back out.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->{$this->parentKey}])) {
$key = $this->getDictionaryKey($model->{$this->parentKey});
if (isset($dictionary[$key])) {
$model->setRelation(
$relation, $this->related->newCollection($dictionary[$key])
);
@@ -306,7 +306,9 @@ class BelongsToMany extends Relation
$dictionary = [];
foreach ($results as $result) {
$dictionary[$result->{$this->accessor}->{$this->foreignPivotKey}][] = $result;
$value = $this->getDictionaryKey($result->{$this->accessor}->{$this->foreignPivotKey});
$dictionary[$value][] = $result;
}
return $dictionary;
@@ -361,7 +363,7 @@ class BelongsToMany extends Relation
{
$this->pivotWheres[] = func_get_args();
return $this->where($this->table.'.'.$column, $operator, $value, $boolean);
return $this->where($this->qualifyPivotColumn($column), $operator, $value, $boolean);
}
/**
@@ -375,7 +377,7 @@ class BelongsToMany extends Relation
*/
public function wherePivotBetween($column, array $values, $boolean = 'and', $not = false)
{
return $this->whereBetween($this->table.'.'.$column, $values, $boolean, $not);
return $this->whereBetween($this->qualifyPivotColumn($column), $values, $boolean, $not);
}
/**
@@ -428,7 +430,7 @@ class BelongsToMany extends Relation
{
$this->pivotWhereIns[] = func_get_args();
return $this->whereIn($this->table.'.'.$column, $values, $boolean, $not);
return $this->whereIn($this->qualifyPivotColumn($column), $values, $boolean, $not);
}
/**
@@ -523,7 +525,7 @@ class BelongsToMany extends Relation
{
$this->pivotWhereNulls[] = func_get_args();
return $this->whereNull($this->table.'.'.$column, $boolean, $not);
return $this->whereNull($this->qualifyPivotColumn($column), $boolean, $not);
}
/**
@@ -562,7 +564,19 @@ class BelongsToMany extends Relation
}
/**
* Find a related model by its primary key or return new instance of the related model.
* 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
@@ -739,6 +753,28 @@ class BelongsToMany extends Relation
throw (new ModelNotFoundException)->setModel(get_class($this->related));
}
/**
* Execute the query and get the first result or call a callback.
*
* @param \Closure|array $columns
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Model|static|mixed
*/
public function firstOr($columns = ['*'], Closure $callback = null)
{
if ($columns instanceof Closure) {
$callback = $columns;
$columns = ['*'];
}
if (! is_null($model = $this->first($columns))) {
return $model;
}
return $callback();
}
/**
* Get the results of the relationship.
*
@@ -760,7 +796,7 @@ class BelongsToMany extends Relation
public function get($columns = ['*'])
{
// First we'll add the proper select columns onto the query so it is run with
// the proper columns. Then, we will get the results and hydrate out pivot
// 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();
@@ -809,7 +845,7 @@ class BelongsToMany extends Relation
$defaults = [$this->foreignPivotKey, $this->relatedPivotKey];
return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
return $this->table.'.'.$column.' as pivot_'.$column;
return $this->qualifyPivotColumn($column).' as pivot_'.$column;
})->unique()->all();
}
@@ -849,6 +885,24 @@ class BelongsToMany extends Relation
});
}
/**
* 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.
*
@@ -858,12 +912,10 @@ class BelongsToMany extends Relation
*/
public function chunk($count, callable $callback)
{
$this->query->addSelect($this->shouldSelect());
return $this->query->chunk($count, function ($results) use ($callback) {
return $this->prepareQueryBuilder()->chunk($count, function ($results, $page) use ($callback) {
$this->hydratePivotRelation($results->all());
return $callback($results);
return $callback($results, $page);
});
}
@@ -878,7 +930,7 @@ class BelongsToMany extends Relation
*/
public function chunkById($count, callable $callback, $column = null, $alias = null)
{
$this->query->addSelect($this->shouldSelect());
$this->prepareQueryBuilder();
$column = $column ?? $this->getRelated()->qualifyColumn(
$this->getRelatedKeyName()
@@ -911,6 +963,44 @@ class BelongsToMany extends Relation
});
}
/**
* 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 $chunkSize
* @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.
*
@@ -918,15 +1008,23 @@ class BelongsToMany extends Relation
*/
public function cursor()
{
$this->query->addSelect($this->shouldSelect());
return $this->query->cursor()->map(function ($model) {
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.
*
@@ -1165,16 +1263,6 @@ class BelongsToMany extends Relation
return $this->getQualifiedForeignPivotKeyName();
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Specify that the pivot table has creation and update timestamps.
*
@@ -1229,7 +1317,7 @@ class BelongsToMany extends Relation
*/
public function getQualifiedForeignPivotKeyName()
{
return $this->table.'.'.$this->foreignPivotKey;
return $this->qualifyPivotColumn($this->foreignPivotKey);
}
/**
@@ -1249,7 +1337,7 @@ class BelongsToMany extends Relation
*/
public function getQualifiedRelatedPivotKeyName()
{
return $this->table.'.'.$this->relatedPivotKey;
return $this->qualifyPivotColumn($this->relatedPivotKey);
}
/**
@@ -1282,6 +1370,16 @@ class BelongsToMany extends Relation
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.
*
@@ -1321,4 +1419,17 @@ class BelongsToMany extends Relation
{
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;
}
}

View File

@@ -83,15 +83,15 @@ trait AsPivot
}
/**
* Set the keys for a save update query.
* Set the keys for a select query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery(Builder $query)
protected function setKeysForSelectQuery($query)
{
if (isset($this->attributes[$this->getKeyName()])) {
return parent::setKeysForSaveQuery($query);
return parent::setKeysForSelectQuery($query);
}
$query->where($this->foreignKey, $this->getOriginal(
@@ -103,6 +103,17 @@ trait AsPivot
));
}
/**
* 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.
*

View File

@@ -116,13 +116,29 @@ trait InteractsWithPivotTable
// have done any attaching or detaching, and if we have we will touch these
// relationships if they are configured to touch on any database updates.
if (count($changes['attached']) ||
count($changes['updated'])) {
count($changes['updated']) ||
count($changes['detached'])) {
$this->touchIfTouching();
}
return $changes;
}
/**
* Sync the intermediate tables with a list of IDs or collection of models with the given pivot values.
*
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param array $values
* @param bool $detaching
* @return array
*/
public function syncWithPivotValues($ids, array $values, bool $detaching = true)
{
return $this->sync(collect($this->parseIds($ids))->mapWithKeys(function ($id) use ($values) {
return [$id => $values];
}), $detaching);
}
/**
* Format the sync / toggle record list so that it is keyed by ID.
*
@@ -431,7 +447,7 @@ trait InteractsWithPivotTable
return 0;
}
$query->whereIn($this->relatedPivotKey, (array) $ids);
$query->whereIn($this->getQualifiedRelatedPivotKeyName(), (array) $ids);
}
// Once we have all of the conditions set on the statement, we are ready
@@ -475,7 +491,7 @@ trait InteractsWithPivotTable
protected function getCurrentlyAttachedPivots()
{
return $this->newPivotQuery()->get()->map(function ($record) {
$class = $this->using ? $this->using : Pivot::class;
$class = $this->using ?: Pivot::class;
$pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true);
@@ -552,7 +568,7 @@ trait InteractsWithPivotTable
$query->whereNull(...$arguments);
}
return $query->where($this->foreignPivotKey, $this->parent->{$this->parentKey});
return $query->where($this->getQualifiedForeignPivotKeyName(), $this->parent->{$this->parentKey});
}
/**

View File

View File

@@ -7,10 +7,13 @@ 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.
*
@@ -53,13 +56,6 @@ class HasManyThrough extends Relation
*/
protected $secondLocalKey;
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new has many through relationship instance.
*
@@ -200,7 +196,7 @@ class HasManyThrough extends Relation
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
$model->setRelation(
$relation, $this->related->newCollection($dictionary[$key])
);
@@ -435,6 +431,22 @@ class HasManyThrough extends Relation
return $this->query->simplePaginate($perPage, $columns, $pageName, $page);
}
/**
* Paginate the given query into a cursor paginator.
*
* @param int|null $perPage
* @param array $columns
* @param string $cursorName
* @param string|null $cursor
* @return \Illuminate\Contracts\Pagination\CursorPaginator
*/
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
{
$this->query->addSelect($this->shouldSelect($columns));
return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor);
}
/**
* Set the select clause for the relation query.
*
@@ -508,6 +520,34 @@ class HasManyThrough extends Relation
});
}
/**
* Query lazily, by chunks of the given size.
*
* @param int $chunkSize
* @return \Illuminate\Support\LazyCollection
*/
public function lazy($chunkSize = 1000)
{
return $this->prepareQueryBuilder()->lazy($chunkSize);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs.
*
* @param int $chunkSize
* @param string|null $column
* @param string|null $alias
* @return \Illuminate\Support\LazyCollection
*/
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
{
$column = $column ?? $this->getRelated()->getQualifiedKeyName();
$alias = $alias ?? $this->getRelated()->getKeyName();
return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias);
}
/**
* Prepare the query builder for query execution.
*
@@ -596,16 +636,6 @@ class HasManyThrough extends Relation
);
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Get the qualified foreign key on the related model.
*

View File

@@ -2,13 +2,18 @@
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
class HasOne extends HasOneOrMany implements SupportsPartialRelations
{
use SupportsDefaultModels;
use ComparesRelatedModels, CanBeOneOfMany, SupportsDefaultModels;
/**
* Get the results of the relationship.
@@ -53,6 +58,59 @@ class HasOne extends HasOneOrMany
return $this->matchOne($models, $results, $relation);
}
/**
* Add the constraints for an internal relationship existence query.
*
* Essentially, these queries compare on column names like "whereColumn".
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
if ($this->isOneOfMany()) {
$this->mergeOneOfManyJoinsTo($query);
}
return parent::getRelationExistenceQuery($query, $parentQuery, $columns);
}
/**
* Add constraints for inner join subselect for one of many relationships.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string|null $column
* @param string|null $aggregate
* @return void
*/
public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null)
{
$query->addSelect($this->foreignKey);
}
/**
* Get the columns that should be selected by the one of many subquery.
*
* @return array|string
*/
public function getOneOfManySubQuerySelectColumns()
{
return $this->foreignKey;
}
/**
* Add join query constraints for one of many relationships.
*
* @param \Illuminate\Database\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.
*
@@ -65,4 +123,15 @@ class HasOne extends HasOneOrMany
$this->getForeignKeyName(), $parent->{$this->localKey}
);
}
/**
* Get the value of the model's foreign key.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
protected function getRelatedKeyFrom(Model $model)
{
return $model->getAttribute($this->getForeignKeyName());
}
}

View File

@@ -5,9 +5,12 @@ 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.
*
@@ -22,13 +25,6 @@ abstract class HasOneOrMany extends Relation
*/
protected $localKey;
/**
* The count of self joins.
*
* @var int
*/
protected static $selfJoinCount = 0;
/**
* Create a new has one or many relationship instance.
*
@@ -60,7 +56,7 @@ abstract class HasOneOrMany extends Relation
}
/**
* Create and return an un-saved instances of the related models.
* Create and return an un-saved instance of the related models.
*
* @param iterable $records
* @return \Illuminate\Database\Eloquent\Collection
@@ -84,9 +80,11 @@ abstract class HasOneOrMany extends Relation
public function addConstraints()
{
if (static::$constraints) {
$this->query->where($this->foreignKey, '=', $this->getParentKey());
$query = $this->getRelationQuery();
$this->query->whereNotNull($this->foreignKey);
$query->where($this->foreignKey, '=', $this->getParentKey());
$query->whereNotNull($this->foreignKey);
}
}
@@ -100,7 +98,7 @@ abstract class HasOneOrMany extends Relation
{
$whereIn = $this->whereInMethod($this->parent, $this->localKey);
$this->query->{$whereIn}(
$this->getRelationQuery()->{$whereIn}(
$this->foreignKey, $this->getKeys($models, $this->localKey)
);
}
@@ -148,7 +146,7 @@ abstract class HasOneOrMany extends Relation
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
$model->setRelation(
$relation, $this->getRelationValue($dictionary, $key, $type)
);
@@ -184,12 +182,12 @@ abstract class HasOneOrMany extends Relation
$foreign = $this->getForeignKeyName();
return $results->mapToDictionary(function ($result) use ($foreign) {
return [$result->{$foreign} => $result];
return [$this->getDictionaryKey($result->{$foreign}) => $result];
})->all();
}
/**
* Find a model by its primary key or return new instance of the related model.
* Find a model by its primary key or return a new instance of the related model.
*
* @param mixed $id
* @param array $columns
@@ -213,10 +211,10 @@ abstract class HasOneOrMany extends Relation
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public function firstOrNew(array $attributes, array $values = [])
public function firstOrNew(array $attributes = [], array $values = [])
{
if (is_null($instance = $this->where($attributes)->first())) {
$instance = $this->related->newInstance($attributes + $values);
$instance = $this->related->newInstance(array_merge($attributes, $values));
$this->setForeignAttributesForCreate($instance);
}
@@ -231,10 +229,10 @@ abstract class HasOneOrMany extends Relation
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
*/
public function firstOrCreate(array $attributes, array $values = [])
public function firstOrCreate(array $attributes = [], array $values = [])
{
if (is_null($instance = $this->where($attributes)->first())) {
$instance = $this->create($attributes + $values);
$instance = $this->create(array_merge($attributes, $values));
}
return $instance;
@@ -299,6 +297,19 @@ abstract class HasOneOrMany extends Relation
});
}
/**
* Create a new instance of the related model. Allow mass-assignment.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreate(array $attributes = [])
{
$attributes[$this->getForeignKeyName()] = $this->getParentKey();
return $this->related->forceCreate($attributes);
}
/**
* Create a Collection of new instances of the related model.
*
@@ -363,16 +374,6 @@ abstract class HasOneOrMany extends Relation
);
}
/**
* Get a relationship join table hash.
*
* @return string
*/
public function getRelationCountHash()
{
return 'laravel_reserved_'.static::$selfJoinCount++;
}
/**
* Get the key for comparing against the parent key in "has" query.
*

View File

@@ -4,11 +4,12 @@ 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 SupportsDefaultModels;
use InteractsWithDictionary, SupportsDefaultModels;
/**
* Get the results of the relationship.
@@ -52,7 +53,7 @@ class HasOneThrough extends HasManyThrough
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model) {
if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
$value = $dictionary[$key];
$model->setRelation(
$relation, reset($value)

View File

@@ -46,4 +46,17 @@ class MorphMany extends MorphOneOrMany
{
return $this->matchMany($models, $results, $relation);
}
/**
* Create a new instance of the related model. Allow mass-assignment.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreate(array $attributes = [])
{
$attributes[$this->getMorphType()] = $this->morphClass;
return parent::forceCreate($attributes);
}
}

View File

@@ -2,13 +2,18 @@
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
class MorphOne extends MorphOneOrMany implements SupportsPartialRelations
{
use SupportsDefaultModels;
use CanBeOneOfMany, ComparesRelatedModels, SupportsDefaultModels;
/**
* Get the results of the relationship.
@@ -53,6 +58,59 @@ class MorphOne extends MorphOneOrMany
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.
*
@@ -65,4 +123,15 @@ class MorphOne extends MorphOneOrMany
->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());
}
}

View File

@@ -48,9 +48,9 @@ abstract class MorphOneOrMany extends HasOneOrMany
public function addConstraints()
{
if (static::$constraints) {
parent::addConstraints();
$this->getRelationQuery()->where($this->morphType, $this->morphClass);
$this->query->where($this->morphType, $this->morphClass);
parent::addConstraints();
}
}
@@ -64,7 +64,7 @@ abstract class MorphOneOrMany extends HasOneOrMany
{
parent::addEagerConstraints($models);
$this->query->where($this->morphType, $this->morphClass);
$this->getRelationQuery()->where($this->morphType, $this->morphClass);
}
/**

View File

@@ -2,7 +2,6 @@
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
class MorphPivot extends Pivot
@@ -31,13 +30,26 @@ class MorphPivot extends Pivot
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery(Builder $query)
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.
*
@@ -62,6 +74,16 @@ class MorphPivot extends Pivot
});
}
/**
* Get the morph type for the pivot.
*
* @return string
*/
public function getMorphType()
{
return $this->morphType;
}
/**
* Set the morph type for the pivot.
*

View File

@@ -6,9 +6,12 @@ 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.
*
@@ -51,6 +54,13 @@ class MorphTo extends BelongsTo
*/
protected $morphableEagerLoadCounts = [];
/**
* A map of constraints to apply for each individual morph type.
*
* @var array
*/
protected $morphableConstraints = [];
/**
* Create a new morph to relationship instance.
*
@@ -90,7 +100,10 @@ class MorphTo extends BelongsTo
{
foreach ($models as $model) {
if ($model->{$this->morphType}) {
$this->dictionary[$model->{$this->morphType}][$model->{$this->foreignKey}][] = $model;
$morphTypeKey = $this->getDictionaryKey($model->{$this->morphType});
$foreignKeyKey = $this->getDictionaryKey($model->{$this->foreignKey});
$this->dictionary[$morphTypeKey][$foreignKeyKey][] = $model;
}
}
}
@@ -133,10 +146,14 @@ class MorphTo extends BelongsTo
(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->getTable().'.'.$ownerKey, $this->gatherKeysByType($type, $instance->getKeyType())
)->get();
}
@@ -144,11 +161,16 @@ class MorphTo extends BelongsTo
* Gather all of the foreign keys for a given type.
*
* @param string $type
* @param string $keyType
* @return array
*/
protected function gatherKeysByType($type)
protected function gatherKeysByType($type, $keyType)
{
return array_keys($this->dictionary[$type]);
return $keyType !== 'string'
? array_keys($this->dictionary[$type])
: array_map(function ($modelId) {
return (string) $modelId;
}, array_filter(array_keys($this->dictionary[$type])));
}
/**
@@ -191,7 +213,7 @@ class MorphTo extends BelongsTo
protected function matchToMorphParents($type, Collection $results)
{
foreach ($results as $result) {
$ownerKey = ! is_null($this->ownerKey) ? $result->{$this->ownerKey} : $result->getKey();
$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) {
@@ -209,8 +231,14 @@ class MorphTo extends BelongsTo
*/
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->getKey() : null
$this->foreignKey, $model instanceof Model ? $model->{$foreignKey} : null
);
$this->parent->setAttribute(
@@ -307,6 +335,21 @@ class MorphTo extends BelongsTo
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.
*

View File

@@ -68,7 +68,7 @@ class MorphToMany extends BelongsToMany
{
parent::addWhereConstraints();
$this->query->where($this->table.'.'.$this->morphType, $this->morphClass);
$this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass);
return $this;
}
@@ -83,7 +83,7 @@ class MorphToMany extends BelongsToMany
{
parent::addEagerConstraints($models);
$this->query->where($this->table.'.'.$this->morphType, $this->morphClass);
$this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass);
}
/**
@@ -111,7 +111,7 @@ class MorphToMany extends BelongsToMany
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
{
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where(
$this->table.'.'.$this->morphType, $this->morphClass
$this->qualifyPivotColumn($this->morphType), $this->morphClass
);
}
@@ -173,7 +173,7 @@ class MorphToMany extends BelongsToMany
$defaults = [$this->foreignPivotKey, $this->relatedPivotKey, $this->morphType];
return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) {
return $this->table.'.'.$column.' as pivot_'.$column;
return $this->qualifyPivotColumn($column).' as pivot_'.$column;
})->unique()->all();
}

View File

View File

@@ -6,6 +6,8 @@ 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;
@@ -49,12 +51,26 @@ abstract class Relation
protected static $constraints = true;
/**
* An array to map class names to their morph names in database.
* 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.
*
@@ -144,6 +160,30 @@ abstract class Relation
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.
*
@@ -213,6 +253,17 @@ abstract class Relation
);
}
/**
* 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.
*
@@ -227,6 +278,16 @@ abstract class Relation
})->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.
*
@@ -322,6 +383,41 @@ abstract class Relation
: '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 $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.
*
@@ -382,13 +478,7 @@ abstract class Relation
return $this->macroCall($method, $parameters);
}
$result = $this->forwardCallTo($this->query, $method, $parameters);
if ($result === $this->query) {
return $this;
}
return $result;
return $this->forwardDecoratedCallTo($this->query, $method, $parameters);
}
/**

View File

@@ -3,7 +3,7 @@
namespace Illuminate\Database\Eloquent;
/**
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withTrashed()
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder 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()
*/
@@ -33,7 +33,9 @@ trait SoftDeletes
*/
public function initializeSoftDeletes()
{
$this->dates[] = $this->getDeletedAtColumn();
if (! isset($this->casts[$this->getDeletedAtColumn()])) {
$this->casts[$this->getDeletedAtColumn()] = 'datetime';
}
}
/**
@@ -62,9 +64,9 @@ trait SoftDeletes
protected function performDeleteOnModel()
{
if ($this->forceDeleting) {
$this->exists = false;
return $this->setKeysForSaveQuery($this->newModelQuery())->forceDelete();
return tap($this->setKeysForSaveQuery($this->newModelQuery())->forceDelete(), function () {
$this->exists = false;
});
}
return $this->runSoftDelete();
@@ -94,6 +96,8 @@ trait SoftDeletes
$query->update($columns);
$this->syncOriginalAttributes(array_keys($columns));
$this->fireModelEvent('trashed', false);
}
/**
@@ -134,6 +138,17 @@ trait SoftDeletes
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.
*

View File

@@ -7,7 +7,7 @@ class SoftDeletingScope implements Scope
/**
* All of the extensions to be added to the builder.
*
* @var array
* @var string[]
*/
protected $extensions = ['Restore', 'WithTrashed', 'WithoutTrashed', 'OnlyTrashed'];

View File

@@ -8,7 +8,7 @@ use Illuminate\Database\Migrations\Migration;
abstract class MigrationEvent implements MigrationEventContract
{
/**
* An migration instance.
* A migration instance.
*
* @var \Illuminate\Database\Migrations\Migration
*/

View File

@@ -2,9 +2,7 @@
namespace Illuminate\Database\Events;
use Illuminate\Contracts\Database\Events\MigrationEvent as MigrationEventContract;
class MigrationsEnded implements MigrationEventContract
class MigrationsEnded extends MigrationsEvent
{
//
}

View File

@@ -2,9 +2,7 @@
namespace Illuminate\Database\Events;
use Illuminate\Contracts\Database\Events\MigrationEvent as MigrationEventContract;
class MigrationsStarted implements MigrationEventContract
class MigrationsStarted extends MigrationsEvent
{
//
}

2
vendor/laravel/framework/src/Illuminate/Database/Grammar.php vendored Normal file → Executable file
View File

@@ -179,7 +179,7 @@ abstract class Grammar
* Get the value of a raw expression.
*
* @param \Illuminate\Database\Query\Expression $expression
* @return string
* @return mixed
*/
public function getValue($expression)
{

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Database;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Database\Console\Migrations\InstallCommand;
@@ -116,7 +117,7 @@ class MigrationServiceProvider extends ServiceProvider implements DeferrableProv
protected function registerMigrateCommand()
{
$this->app->singleton('command.migrate', function ($app) {
return new MigrateCommand($app['migrator']);
return new MigrateCommand($app['migrator'], $app[Dispatcher::class]);
});
}

View File

@@ -169,6 +169,18 @@ class DatabaseMigrationRepository implements MigrationRepositoryInterface
return $schema->hasTable($this->table);
}
/**
* Delete the migration repository data store.
*
* @return void
*/
public function deleteRepository()
{
$schema = $this->getConnection()->getSchemaBuilder();
$schema->drop($this->table);
}
/**
* Get a query builder for the migration table.
*

View File

View File

@@ -63,9 +63,12 @@ class MigrationCreator
// various place-holders, save the file, and run the post create event.
$stub = $this->getStub($table, $create);
$path = $this->getPath($name, $path);
$this->files->ensureDirectoryExists(dirname($path));
$this->files->put(
$path = $this->getPath($name, $path),
$this->populateStub($name, $stub, $table)
$path, $this->populateStub($name, $stub, $table)
);
// Next, we will fire any hooks that are supposed to fire after a migration is

View File

@@ -12,7 +12,7 @@ interface MigrationRepositoryInterface
public function getRan();
/**
* Get list of migrations.
* Get the list of migrations.
*
* @param int $steps
* @return array
@@ -71,6 +71,13 @@ interface MigrationRepositoryInterface
*/
public function repositoryExists();
/**
* Delete the migration repository data store.
*
* @return void
*/
public function deleteRepository();
/**
* Set the information source to gather data.
*

View File

@@ -2,6 +2,7 @@
namespace Illuminate\Database\Migrations;
use Doctrine\DBAL\Schema\SchemaException;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Events\MigrationEnded;
@@ -13,6 +14,7 @@ use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionClass;
use Symfony\Component\Console\Output\OutputInterface;
class Migrator
@@ -156,7 +158,7 @@ class Migrator
$step = $options['step'] ?? false;
$this->fireMigrationEvent(new MigrationsStarted);
$this->fireMigrationEvent(new MigrationsStarted('up'));
// Once we have the array of migrations, we will spin through them and run the
// migrations "up" so the changes are made to the databases. We'll then log
@@ -169,7 +171,7 @@ class Migrator
}
}
$this->fireMigrationEvent(new MigrationsEnded);
$this->fireMigrationEvent(new MigrationsEnded('up'));
}
/**
@@ -185,9 +187,9 @@ class Migrator
// First we will resolve a "real" instance of the migration class from this
// migration file name. Once we have the instances we can run the actual
// command such as "up" or "down", or we can just simulate the action.
$migration = $this->resolve(
$name = $this->getMigrationName($file)
);
$migration = $this->resolvePath($file);
$name = $this->getMigrationName($file);
if ($pretend) {
return $this->pretendToRun($migration, 'up');
@@ -199,14 +201,14 @@ class Migrator
$this->runMigration($migration, 'up');
$runTime = round(microtime(true) - $startTime, 2);
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
// Once we have run a migrations class, we will log that it was run in this
// repository so that we don't try to run it next time we do a migration
// in the application. A migration repository keeps the migrate order.
$this->repository->log($name, $batch);
$this->note("<info>Migrated:</info> {$name} ({$runTime} seconds)");
$this->note("<info>Migrated:</info> {$name} ({$runTime}ms)");
}
/**
@@ -263,7 +265,7 @@ class Migrator
$this->requireFiles($files = $this->getMigrationFiles($paths));
$this->fireMigrationEvent(new MigrationsStarted);
$this->fireMigrationEvent(new MigrationsStarted('down'));
// Next we will run through all of the migrations and call the "down" method
// which will reverse each migration in order. This getLast method on the
@@ -285,7 +287,7 @@ class Migrator
);
}
$this->fireMigrationEvent(new MigrationsEnded);
$this->fireMigrationEvent(new MigrationsEnded('down'));
return $rolledBack;
}
@@ -348,9 +350,9 @@ class Migrator
// First we will get the file name of the migration so we can resolve out an
// instance of the migration. Once we get an instance we can either run a
// pretend execution of the migration or we can run the real migration.
$instance = $this->resolve(
$name = $this->getMigrationName($file)
);
$instance = $this->resolvePath($file);
$name = $this->getMigrationName($file);
$this->note("<comment>Rolling back:</comment> {$name}");
@@ -362,14 +364,14 @@ class Migrator
$this->runMigration($instance, 'down');
$runTime = round(microtime(true) - $startTime, 2);
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
// Once we have successfully run the migration "down" we will remove it from
// the migration repository so it will be considered to have not been run
// by the application then will be able to fire by any later operation.
$this->repository->delete($migration);
$this->note("<info>Rolled back:</info> {$name} ({$runTime} seconds)");
$this->note("<info>Rolled back:</info> {$name} ({$runTime}ms)");
}
/**
@@ -385,11 +387,11 @@ class Migrator
$migration->getConnection()
);
$callback = function () use ($migration, $method) {
$callback = function () use ($connection, $migration, $method) {
if (method_exists($migration, $method)) {
$this->fireMigrationEvent(new MigrationStarted($migration, $method));
$migration->{$method}();
$this->runMethod($connection, $migration, $method);
$this->fireMigrationEvent(new MigrationEnded($migration, $method));
}
@@ -410,10 +412,22 @@ class Migrator
*/
protected function pretendToRun($migration, $method)
{
foreach ($this->getQueries($migration, $method) as $query) {
try {
foreach ($this->getQueries($migration, $method) as $query) {
$name = get_class($migration);
$reflectionClass = new ReflectionClass($migration);
if ($reflectionClass->isAnonymous()) {
$name = $this->getMigrationName($reflectionClass->getFileName());
}
$this->note("<info>{$name}:</info> {$query['query']}");
}
} catch (SchemaException $e) {
$name = get_class($migration);
$this->note("<info>{$name}:</info> {$query['query']}");
$this->note("<info>{$name}:</info> failed to dump queries. This may be due to changing database columns using Doctrine, which is not supported while pretending to run migrations.");
}
}
@@ -433,13 +447,34 @@ class Migrator
$migration->getConnection()
);
return $db->pretend(function () use ($migration, $method) {
return $db->pretend(function () use ($db, $migration, $method) {
if (method_exists($migration, $method)) {
$migration->{$method}();
$this->runMethod($db, $migration, $method);
}
});
}
/**
* Run a migration method on the given connection.
*
* @param \Illuminate\Database\Connection $connection
* @param object $migration
* @param string $method
* @return void
*/
protected function runMethod($connection, $migration, $method)
{
$previousConnection = $this->resolver->getDefaultConnection();
try {
$this->resolver->setDefaultConnection($connection->getName());
$migration->{$method}();
} finally {
$this->resolver->setDefaultConnection($previousConnection);
}
}
/**
* Resolve a migration instance from a file.
*
@@ -448,11 +483,41 @@ class Migrator
*/
public function resolve($file)
{
$class = Str::studly(implode('_', array_slice(explode('_', $file), 4)));
$class = $this->getMigrationClass($file);
return new $class;
}
/**
* Resolve a migration instance from a migration path.
*
* @param string $path
* @return object
*/
protected function resolvePath(string $path)
{
$class = $this->getMigrationClass($this->getMigrationName($path));
if (class_exists($class) && realpath($path) == (new ReflectionClass($class))->getFileName()) {
return new $class;
}
$migration = $this->files->getRequire($path);
return is_object($migration) ? $migration : new $class;
}
/**
* Generate a migration class name based on the migration file name.
*
* @param string $migrationName
* @return string
*/
protected function getMigrationClass(string $migrationName): string
{
return Str::studly(implode('_', array_slice(explode('_', $migrationName), 4)));
}
/**
* Get all of the migration files in a given path.
*
@@ -608,6 +673,26 @@ class Migrator
return $this->repository->repositoryExists();
}
/**
* Determine if any migrations have been run.
*
* @return bool
*/
public function hasRunAnyMigrations()
{
return $this->repositoryExists() && count($this->repository->getRan()) > 0;
}
/**
* Delete the migration repository data store.
*
* @return void
*/
public function deleteRepository()
{
return $this->repository->deleteRepository();
}
/**
* Get the file system instance.
*

View File

31
vendor/laravel/framework/src/Illuminate/Database/MySqlConnection.php vendored Normal file → Executable file
View File

@@ -3,13 +3,28 @@
namespace Illuminate\Database;
use Doctrine\DBAL\Driver\PDOMySql\Driver as DoctrineDriver;
use Doctrine\DBAL\Version;
use Illuminate\Database\PDO\MySqlDriver;
use Illuminate\Database\Query\Grammars\MySqlGrammar as QueryGrammar;
use Illuminate\Database\Query\Processors\MySqlProcessor;
use Illuminate\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar;
use Illuminate\Database\Schema\MySqlBuilder;
use Illuminate\Database\Schema\MySqlSchemaState;
use Illuminate\Filesystem\Filesystem;
use PDO;
class MySqlConnection extends Connection
{
/**
* Determine if the connected database is a MariaDB database.
*
* @return bool
*/
public function isMaria()
{
return strpos($this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), 'MariaDB') !== false;
}
/**
* Get the default query grammar instance.
*
@@ -44,6 +59,18 @@ class MySqlConnection extends Connection
return $this->withTablePrefix(new SchemaGrammar);
}
/**
* Get the schema state for the connection.
*
* @param \Illuminate\Filesystem\Filesystem|null $files
* @param callable|null $processFactory
* @return \Illuminate\Database\Schema\MySqlSchemaState
*/
public function getSchemaState(Filesystem $files = null, callable $processFactory = null)
{
return new MySqlSchemaState($this, $files, $processFactory);
}
/**
* Get the default post processor instance.
*
@@ -57,10 +84,10 @@ class MySqlConnection extends Connection
/**
* Get the Doctrine DBAL driver.
*
* @return \Doctrine\DBAL\Driver\PDOMySql\Driver
* @return \Doctrine\DBAL\Driver\PDOMySql\Driver|\Illuminate\Database\PDO\MySqlDriver
*/
protected function getDoctrineDriver()
{
return new DoctrineDriver;
return class_exists(Version::class) ? new DoctrineDriver : new MySqlDriver;
}
}

View File

@@ -3,10 +3,14 @@
namespace Illuminate\Database;
use Doctrine\DBAL\Driver\PDOPgSql\Driver as DoctrineDriver;
use Doctrine\DBAL\Version;
use Illuminate\Database\PDO\PostgresDriver;
use Illuminate\Database\Query\Grammars\PostgresGrammar as QueryGrammar;
use Illuminate\Database\Query\Processors\PostgresProcessor;
use Illuminate\Database\Schema\Grammars\PostgresGrammar as SchemaGrammar;
use Illuminate\Database\Schema\PostgresBuilder;
use Illuminate\Database\Schema\PostgresSchemaState;
use Illuminate\Filesystem\Filesystem;
use PDO;
class PostgresConnection extends Connection
@@ -71,6 +75,18 @@ class PostgresConnection extends Connection
return $this->withTablePrefix(new SchemaGrammar);
}
/**
* Get the schema state for the connection.
*
* @param \Illuminate\Filesystem\Filesystem|null $files
* @param callable|null $processFactory
* @return \Illuminate\Database\Schema\PostgresSchemaState
*/
public function getSchemaState(Filesystem $files = null, callable $processFactory = null)
{
return new PostgresSchemaState($this, $files, $processFactory);
}
/**
* Get the default post processor instance.
*
@@ -84,10 +100,10 @@ class PostgresConnection extends Connection
/**
* Get the Doctrine DBAL driver.
*
* @return \Doctrine\DBAL\Driver\PDOPgSql\Driver
* @return \Doctrine\DBAL\Driver\PDOPgSql\Driver|\Illuminate\Database\PDO\PostgresDriver
*/
protected function getDoctrineDriver()
{
return new DoctrineDriver;
return class_exists(Version::class) ? new DoctrineDriver : new PostgresDriver;
}
}

363
vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php vendored Normal file → Executable file
View File

@@ -2,10 +2,12 @@
namespace Illuminate\Database\Query;
use BackedEnum;
use Closure;
use DateTimeInterface;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Concerns\BuildsQueries;
use Illuminate\Database\Concerns\ExplainsQueries;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\Relation;
@@ -19,11 +21,12 @@ use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use LogicException;
use RuntimeException;
class Builder
{
use BuildsQueries, ForwardsCalls, Macroable {
use BuildsQueries, ExplainsQueries, ForwardsCalls, Macroable {
__call as macroCall;
}
@@ -180,21 +183,37 @@ class Builder
public $lock;
/**
* All of the available clause operators.
* The callbacks that should be invoked before the query is executed.
*
* @var array
*/
public $beforeQueryCallbacks = [];
/**
* All of the available clause operators.
*
* @var string[]
*/
public $operators = [
'=', '<', '>', '<=', '>=', '<>', '!=', '<=>',
'like', 'like binary', 'not like', 'ilike',
'&', '|', '^', '<<', '>>',
'&', '|', '^', '<<', '>>', '&~',
'rlike', 'not rlike', 'regexp', 'not regexp',
'~', '~*', '!~', '!~*', 'similar to',
'not similar to', 'not ilike', '~~*', '!~~*',
];
/**
* Whether use write pdo for select.
* All of the available bitwise operators.
*
* @var string[]
*/
public $bitwiseOperators = [
'&', '|', '^', '<<', '>>', '&~',
];
/**
* Whether to use write pdo for the select.
*
* @var bool
*/
@@ -243,7 +262,7 @@ class Builder
/**
* Add a subselect expression to the query.
*
* @param \Closure|$this|string $query
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query
* @param string $as
* @return $this
*
@@ -339,6 +358,8 @@ class Builder
protected function parseSub($query)
{
if ($query instanceof self || $query instanceof EloquentBuilder || $query instanceof Relation) {
$query = $this->prependDatabaseNameIfCrossDatabaseQuery($query);
return [$query->toSql(), $query->getBindings()];
} elseif (is_string($query)) {
return [$query, []];
@@ -349,6 +370,26 @@ class Builder
}
}
/**
* Prepend the database name if the given query is on another database.
*
* @param mixed $query
* @return mixed
*/
protected function prependDatabaseNameIfCrossDatabaseQuery($query)
{
if ($query->getConnection()->getDatabaseName() !==
$this->getConnection()->getDatabaseName()) {
$databaseName = $query->getConnection()->getDatabaseName();
if (strpos($query->from, $databaseName) !== 0 && strpos($query->from, '.') === false) {
$query->from($databaseName.'.'.$query->from);
}
}
return $query;
}
/**
* Add a new select column to the query.
*
@@ -377,6 +418,7 @@ class Builder
/**
* Force the query to only return distinct results.
*
* @param mixed ...$distinct
* @return $this
*/
public function distinct()
@@ -468,7 +510,7 @@ class Builder
/**
* Add a subquery join clause to the query.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $query
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query
* @param string $as
* @param \Closure|string $first
* @param string|null $operator
@@ -521,7 +563,7 @@ class Builder
/**
* Add a subquery left join to the query.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $query
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query
* @param string $as
* @param \Closure|string $first
* @param string|null $operator
@@ -564,7 +606,7 @@ class Builder
/**
* Add a subquery right join to the query.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $query
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query
* @param string $as
* @param \Closure|string $first
* @param string|null $operator
@@ -596,6 +638,26 @@ class Builder
return $this;
}
/**
* Add a subquery cross join to the query.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $query
* @param string $as
* @return $this
*/
public function crossJoinSub($query, $as)
{
[$query, $bindings] = $this->createSub($query);
$expression = '('.$query.') as '.$this->grammar->wrapTable($as);
$this->addBinding($bindings, 'join');
$this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression));
return $this;
}
/**
* Get a new join clause.
*
@@ -701,6 +763,10 @@ class Builder
}
}
if ($this->isBitwiseOperator($operator)) {
$type = 'Bitwise';
}
// Now that we are working with just a simple query we can put the elements
// in our array and add the query binding to our array of bindings that
// will be bound to each SQL statements when it is finally executed.
@@ -780,8 +846,20 @@ class Builder
*/
protected function invalidOperator($operator)
{
return ! in_array(strtolower($operator), $this->operators, true) &&
! in_array(strtolower($operator), $this->grammar->getOperators(), true);
return ! is_string($operator) || (! in_array(strtolower($operator), $this->operators, true) &&
! in_array(strtolower($operator), $this->grammar->getOperators(), true));
}
/**
* Determine if the operator is a bitwise operator.
*
* @param string $operator
* @return bool
*/
protected function isBitwiseOperator($operator)
{
return in_array(strtolower($operator), $this->bitwiseOperators, true) ||
in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true);
}
/**
@@ -1043,7 +1121,7 @@ class Builder
/**
* Add an "or where null" clause to the query.
*
* @param string $column
* @param string|array $column
* @return $this
*/
public function orWhereNull($column)
@@ -1066,7 +1144,7 @@ class Builder
/**
* Add a where between statement to the query.
*
* @param string $column
* @param string|\Illuminate\Database\Query\Expression $column
* @param array $values
* @param string $boolean
* @param bool $not
@@ -1189,7 +1267,7 @@ class Builder
/**
* Add a "where date" statement to the query.
*
* @param string $column
* @param \Illuminate\Database\Query\Expression|string $column
* @param string $operator
* @param \DateTimeInterface|string|null $value
* @param string $boolean
@@ -1768,6 +1846,39 @@ class Builder
$this->where(Str::snake($segment), '=', $parameters[$index], $bool);
}
/**
* Add a "where fulltext" clause to the query.
*
* @param string|string[] $columns
* @param string $value
* @param string $boolean
* @return $this
*/
public function whereFullText($columns, $value, array $options = [], $boolean = 'and')
{
$type = 'Fulltext';
$columns = (array) $columns;
$this->wheres[] = compact('type', 'columns', 'value', 'options', 'boolean');
$this->addBinding($value);
return $this;
}
/**
* Add a "or where fulltext" clause to the query.
*
* @param string|string[] $columns
* @param string $value
* @return $this
*/
public function orWhereFullText($columns, $value, array $options = [])
{
return $this->whereFulltext($columns, $value, $options, 'or');
}
/**
* Add a "group by" clause to the query.
*
@@ -1829,6 +1940,10 @@ class Builder
[$value, $operator] = [$operator, '='];
}
if ($this->isBitwiseOperator($operator)) {
$type = 'Bitwise';
}
$this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
if (! $value instanceof Expression) {
@@ -1909,7 +2024,7 @@ class Builder
/**
* Add an "order by" clause to the query.
*
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column
* @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column
* @param string $direction
* @return $this
*
@@ -1942,7 +2057,7 @@ class Builder
/**
* Add a descending "order by" clause to the query.
*
* @param string $column
* @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column
* @return $this
*/
public function orderByDesc($column)
@@ -1953,7 +2068,7 @@ class Builder
/**
* Add an "order by" clause for a timestamp to the query.
*
* @param string $column
* @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column
* @return $this
*/
public function latest($column = 'created_at')
@@ -1964,7 +2079,7 @@ class Builder
/**
* Add an "order by" clause for a timestamp to the query.
*
* @param string $column
* @param \Closure|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string $column
* @return $this
*/
public function oldest($column = 'created_at')
@@ -2110,7 +2225,7 @@ class Builder
/**
* Remove all existing orders and optionally add a new order.
*
* @param string|null $column
* @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Query\Expression|string|null $column
* @param string $direction
* @return $this
*/
@@ -2211,6 +2326,33 @@ class Builder
return $this->lock(false);
}
/**
* Register a closure to be invoked before the query is executed.
*
* @param callable $callback
* @return $this
*/
public function beforeQuery(callable $callback)
{
$this->beforeQueryCallbacks[] = $callback;
return $this;
}
/**
* Invoke the "before query" modification callbacks.
*
* @return void
*/
public function applyBeforeQueryCallbacks()
{
foreach ($this->beforeQueryCallbacks as $callback) {
$callback($this);
}
$this->beforeQueryCallbacks = [];
}
/**
* Get the SQL representation of the query.
*
@@ -2218,6 +2360,8 @@ class Builder
*/
public function toSql()
{
$this->applyBeforeQueryCallbacks();
return $this->grammar->compileSelect($this);
}
@@ -2317,6 +2461,43 @@ class Builder
]);
}
/**
* Get a paginator only supporting simple next and previous links.
*
* This is more efficient on larger data-sets, etc.
*
* @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 = 15, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
{
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)
{
$this->enforceOrderBy();
return collect($this->orders ?? $this->unionOrders ?? [])->filter(function ($order) {
return Arr::has($order, 'direction');
})->when($shouldReverse, function (Collection $orders) {
return $orders->map(function ($order) {
$order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
return $order;
});
})->values();
}
/**
* Get the count of the total records for the paginator.
*
@@ -2427,7 +2608,7 @@ class Builder
}
/**
* Get an array with the values of a given column.
* Get a collection instance containing the values of a given column.
*
* @param string $column
* @param string|null $key
@@ -2549,6 +2730,8 @@ class Builder
*/
public function exists()
{
$this->applyBeforeQueryCallbacks();
$results = $this->connection->select(
$this->grammar->compileExists($this), $this->getBindings(), ! $this->useWritePdo
);
@@ -2674,8 +2857,8 @@ class Builder
*/
public function aggregate($function, $columns = ['*'])
{
$results = $this->cloneWithout($this->unions ? [] : ['columns'])
->cloneWithoutBindings($this->unions ? [] : ['select'])
$results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns'])
->cloneWithoutBindings($this->unions || $this->havings ? [] : ['select'])
->setAggregate($function, $columns)
->get($columns);
@@ -2758,7 +2941,7 @@ class Builder
}
/**
* Insert a new record into the database.
* Insert new records into the database.
*
* @param array $values
* @return bool
@@ -2787,6 +2970,8 @@ class Builder
}
}
$this->applyBeforeQueryCallbacks();
// Finally, we will run this query against the database connection and return
// the results. We will need to also flatten these bindings before running
// the query so they are all in one huge, flattened array for execution.
@@ -2797,7 +2982,7 @@ class Builder
}
/**
* Insert a new record into the database while ignoring errors.
* Insert new records into the database while ignoring errors.
*
* @param array $values
* @return int
@@ -2817,6 +3002,8 @@ class Builder
}
}
$this->applyBeforeQueryCallbacks();
return $this->connection->affectingStatement(
$this->grammar->compileInsertOrIgnore($this, $values),
$this->cleanBindings(Arr::flatten($values, 1))
@@ -2832,6 +3019,8 @@ class Builder
*/
public function insertGetId(array $values, $sequence = null)
{
$this->applyBeforeQueryCallbacks();
$sql = $this->grammar->compileInsertGetId($this, $values, $sequence);
$values = $this->cleanBindings($values);
@@ -2848,6 +3037,8 @@ class Builder
*/
public function insertUsing(array $columns, $query)
{
$this->applyBeforeQueryCallbacks();
[$sql, $bindings] = $this->createSub($query);
return $this->connection->affectingStatement(
@@ -2857,13 +3048,15 @@ class Builder
}
/**
* Update a record in the database.
* Update records in the database.
*
* @param array $values
* @return int
*/
public function update(array $values)
{
$this->applyBeforeQueryCallbacks();
$sql = $this->grammar->compileUpdate($this, $values);
return $this->connection->update($sql, $this->cleanBindings(
@@ -2871,6 +3064,27 @@ class Builder
));
}
/**
* Update records in a PostgreSQL database using the update from syntax.
*
* @param array $values
* @return int
*/
public function updateFrom(array $values)
{
if (! method_exists($this->grammar, 'compileUpdateFrom')) {
throw new LogicException('This database engine does not support the updateFrom method.');
}
$this->applyBeforeQueryCallbacks();
$sql = $this->grammar->compileUpdateFrom($this, $values);
return $this->connection->update($sql, $this->cleanBindings(
$this->grammar->prepareBindingsForUpdateFrom($this->bindings, $values)
));
}
/**
* Insert or update a record matching the attributes, and fill it with values.
*
@@ -2891,6 +3105,51 @@ class Builder
return (bool) $this->limit(1)->update($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;
} elseif ($update === []) {
return (int) $this->insert($values);
}
if (! is_array(reset($values))) {
$values = [$values];
} else {
foreach ($values as $key => $value) {
ksort($value);
$values[$key] = $value;
}
}
if (is_null($update)) {
$update = array_keys(reset($values));
}
$this->applyBeforeQueryCallbacks();
$bindings = $this->cleanBindings(array_merge(
Arr::flatten($values, 1),
collect($update)->reject(function ($value, $key) {
return is_int($key);
})->all()
));
return $this->connection->affectingStatement(
$this->grammar->compileUpsert($this, $values, (array) $uniqueBy, $update),
$bindings
);
}
/**
* Increment a column's value by a given amount.
*
@@ -2938,7 +3197,7 @@ class Builder
}
/**
* Delete a record from the database.
* Delete records from the database.
*
* @param mixed $id
* @return int
@@ -2952,6 +3211,8 @@ class Builder
$this->where($this->from.'.id', '=', $id);
}
$this->applyBeforeQueryCallbacks();
return $this->connection->delete(
$this->grammar->compileDelete($this), $this->cleanBindings(
$this->grammar->prepareBindingsForDelete($this->bindings)
@@ -2966,6 +3227,8 @@ class Builder
*/
public function truncate()
{
$this->applyBeforeQueryCallbacks();
foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) {
$this->connection->statement($sql, $bindings);
}
@@ -3058,14 +3321,32 @@ class Builder
}
if (is_array($value)) {
$this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value));
$this->bindings[$type] = array_values(array_map(
[$this, 'castBinding'],
array_merge($this->bindings[$type], $value),
));
} else {
$this->bindings[$type][] = $value;
$this->bindings[$type][] = $this->castBinding($value);
}
return $this;
}
/**
* Cast the given binding value.
*
* @param mixed $value
* @return mixed
*/
public function castBinding($value)
{
if (function_exists('enum_exists') && $value instanceof BackedEnum) {
return $value->value;
}
return $value;
}
/**
* Merge an array of bindings into our bindings.
*
@@ -3085,11 +3366,15 @@ class Builder
* @param array $bindings
* @return array
*/
protected function cleanBindings(array $bindings)
public function cleanBindings(array $bindings)
{
return array_values(array_filter($bindings, function ($binding) {
return ! $binding instanceof Expression;
}));
return collect($bindings)
->reject(function ($binding) {
return $binding instanceof Expression;
})
->map([$this, 'castBinding'])
->values()
->all();
}
/**
@@ -3169,6 +3454,16 @@ class Builder
$value instanceof Closure;
}
/**
* Clone the query.
*
* @return static
*/
public function clone()
{
return clone $this;
}
/**
* Clone the query without the given properties.
*
@@ -3177,7 +3472,7 @@ class Builder
*/
public function cloneWithout(array $properties)
{
return tap(clone $this, function ($clone) use ($properties) {
return tap($this->clone(), function ($clone) use ($properties) {
foreach ($properties as $property) {
$clone->{$property} = null;
}
@@ -3192,7 +3487,7 @@ class Builder
*/
public function cloneWithoutBindings(array $except)
{
return tap(clone $this, function ($clone) use ($except) {
return tap($this->clone(), function ($clone) use ($except) {
foreach ($except as $type) {
$clone->bindings[$type] = [];
}
@@ -3214,7 +3509,7 @@ class Builder
/**
* Die and dump the current SQL and bindings.
*
* @return void
* @return never
*/
public function dd()
{

View File

View File

@@ -19,10 +19,17 @@ class Grammar extends BaseGrammar
protected $operators = [];
/**
* The components that make up a select clause.
* The grammar specific bitwise operators.
*
* @var array
*/
protected $bitwiseOperators = [];
/**
* The components that make up a select clause.
*
* @var string[]
*/
protected $selectComponents = [
'aggregate',
'columns',
@@ -45,7 +52,7 @@ class Grammar extends BaseGrammar
*/
public function compileSelect(Builder $query)
{
if ($query->unions && $query->aggregate) {
if (($query->unions || $query->havings) && $query->aggregate) {
return $this->compileUnionAggregate($query);
}
@@ -181,7 +188,7 @@ class Grammar extends BaseGrammar
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileWheres(Builder $query)
public function compileWheres(Builder $query)
{
// Each type of where clauses has its own compiler function which is responsible
// for actually creating the where clauses SQL. This helps keep the code nice
@@ -250,7 +257,21 @@ class Grammar extends BaseGrammar
{
$value = $this->parameter($where['value']);
return $this->wrap($where['column']).' '.$where['operator'].' '.$value;
$operator = str_replace('?', '??', $where['operator']);
return $this->wrap($where['column']).' '.$operator.' '.$value;
}
/**
* Compile a bitwise operator where clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBitwise(Builder $query, $where)
{
return $this->whereBasic($query, $where);
}
/**
@@ -457,7 +478,7 @@ class Grammar extends BaseGrammar
}
/**
* Compile a where clause comparing two columns..
* Compile a where clause comparing two columns.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
@@ -569,7 +590,8 @@ class Grammar extends BaseGrammar
$not = $where['not'] ? 'not ' : '';
return $not.$this->compileJsonContains(
$where['column'], $this->parameter($where['value'])
$where['column'],
$this->parameter($where['value'])
);
}
@@ -608,7 +630,9 @@ class Grammar extends BaseGrammar
protected function whereJsonLength(Builder $query, $where)
{
return $this->compileJsonLength(
$where['column'], $where['operator'], $this->parameter($where['value'])
$where['column'],
$where['operator'],
$this->parameter($where['value'])
);
}
@@ -627,6 +651,18 @@ class Grammar extends BaseGrammar
throw new RuntimeException('This database engine does not support JSON length operations.');
}
/**
* Compile a "where fulltext" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
public function whereFullText(Builder $query, $where)
{
throw new RuntimeException('This database engine does not support fulltext search operations.');
}
/**
* Compile the "group by" portions of the query.
*
@@ -993,6 +1029,22 @@ class Grammar extends BaseGrammar
return "update {$table} {$joins} set {$columns} {$where}";
}
/**
* Compile an "upsert" statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param array $uniqueBy
* @param array $update
* @return string
*
* @throws \RuntimeException
*/
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
throw new RuntimeException('This database engine does not support upserts.');
}
/**
* Prepare the bindings for an update statement.
*
@@ -1266,4 +1318,14 @@ class Grammar extends BaseGrammar
{
return $this->operators;
}
/**
* Get the grammar specific bitwise operators.
*
* @return array
*/
public function getBitwiseOperators()
{
return $this->bitwiseOperators;
}
}

View File

@@ -10,17 +10,16 @@ class MySqlGrammar extends Grammar
/**
* The grammar specific operators.
*
* @var array
* @var string[]
*/
protected $operators = ['sounds like'];
/**
* Add a "where null" clause to the query.
*
* @param string|array $columns
* @param string $boolean
* @param bool $not
* @return $this
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNull(Builder $query, $where)
{
@@ -36,9 +35,9 @@ class MySqlGrammar extends Grammar
/**
* Add a "where not null" clause to the query.
*
* @param string|array $columns
* @param string $boolean
* @return $this
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNotNull(Builder $query, $where)
{
@@ -51,6 +50,30 @@ class MySqlGrammar extends Grammar
return parent::whereNotNull($query, $where);
}
/**
* Compile a "where fulltext" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
public function whereFullText(Builder $query, $where)
{
$columns = $this->columnize($where['columns']);
$value = $this->parameter($where['value']);
$mode = ($where['options']['mode'] ?? []) === 'boolean'
? ' in boolean mode'
: ' in natural language mode';
$expanded = ($where['options']['expanded'] ?? []) && ($where['options']['mode'] ?? []) !== 'boolean'
? ' with query expansion'
: '';
return "match ({$columns}) against (".$value."{$mode}{$expanded})";
}
/**
* Compile an insert ignore statement into SQL.
*
@@ -153,6 +176,28 @@ class MySqlGrammar extends Grammar
})->implode(', ');
}
/**
* Compile an "upsert" statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param array $uniqueBy
* @param array $update
* @return string
*/
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
$sql = $this->compileInsert($query, $values).' on duplicate key update ';
$columns = collect($update)->map(function ($value, $key) {
return is_numeric($key)
? $this->wrap($value).' = values('.$this->wrap($value).')'
: $this->wrap($key).' = '.$this->parameter($value);
})->implode(', ');
return $sql.$columns;
}
/**
* Prepare a JSON column being updated using the JSON_SET function.
*

View File

@@ -11,16 +11,25 @@ class PostgresGrammar extends Grammar
/**
* All of the available clause operators.
*
* @var array
* @var string[]
*/
protected $operators = [
'=', '<', '>', '<=', '>=', '<>', '!=',
'like', 'not like', 'between', 'ilike', 'not ilike',
'~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
'&&', '@>', '<@', '?', '?|', '?&', '||', '-', '-', '#-',
'&&', '@>', '<@', '?', '?|', '?&', '||', '-', '@?', '@@', '#-',
'is distinct from', 'is not distinct from',
];
/**
* The grammar specific bitwise operators.
*
* @var array
*/
protected $bitwiseOperators = [
'~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
];
/**
* {@inheritdoc}
*
@@ -42,6 +51,22 @@ class PostgresGrammar extends Grammar
return parent::whereBasic($query, $where);
}
/**
* {@inheritdoc}
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBitwise(Builder $query, $where)
{
$value = $this->parameter($where['value']);
$operator = str_replace('?', '??', $where['operator']);
return '('.$this->wrap($where['column']).' '.$operator.' '.$value.')::bool';
}
/**
* Compile a "where date" clause.
*
@@ -85,6 +110,71 @@ class PostgresGrammar extends Grammar
return 'extract('.$type.' from '.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
}
/**
* Compile a "where fulltext" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
public function whereFullText(Builder $query, $where)
{
$language = $where['options']['language'] ?? 'english';
if (! in_array($language, $this->validFullTextLanguages())) {
$language = 'english';
}
$columns = collect($where['columns'])->map(function ($column) use ($language) {
return "to_tsvector('{$language}', {$this->wrap($column)})";
})->implode(' || ');
$mode = 'plainto_tsquery';
if (($where['options']['mode'] ?? []) === 'phrase') {
$mode = 'phraseto_tsquery';
}
if (($where['options']['mode'] ?? []) === 'websearch') {
$mode = 'websearch_to_tsquery';
}
return "({$columns}) @@ {$mode}('{$language}', {$this->parameter($where['value'])})";
}
/**
* Get an array of valid full text languages.
*
* @return array
*/
protected function validFullTextLanguages()
{
return [
'simple',
'arabic',
'danish',
'dutch',
'english',
'finnish',
'french',
'german',
'hungarian',
'indonesian',
'irish',
'italian',
'lithuanian',
'nepali',
'norwegian',
'portuguese',
'romanian',
'russian',
'spanish',
'swedish',
'tamil',
'turkish',
];
}
/**
* Compile the "select *" portion of the query.
*
@@ -141,6 +231,36 @@ class PostgresGrammar extends Grammar
return 'json_array_length(('.$column.')::json) '.$operator.' '.$value;
}
/**
* {@inheritdoc}
*
* @param array $having
* @return string
*/
protected function compileHaving(array $having)
{
if ($having['type'] === 'Bitwise') {
return $this->compileHavingBitwise($having);
}
return parent::compileHaving($having);
}
/**
* Compile a having clause involving a bitwise operator.
*
* @param array $having
* @return string
*/
protected function compileHavingBitwise($having)
{
$column = $this->wrap($having['column']);
$parameter = $this->parameter($having['value']);
return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.')::bool';
}
/**
* Compile the lock into SQL.
*
@@ -218,6 +338,30 @@ class PostgresGrammar extends Grammar
})->implode(', ');
}
/**
* Compile an "upsert" statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param array $uniqueBy
* @param array $update
* @return string
*/
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
$sql = $this->compileInsert($query, $values);
$sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set ';
$columns = collect($update)->map(function ($value, $key) {
return is_numeric($key)
? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value)
: $this->wrap($key).' = '.$this->parameter($value);
})->implode(', ');
return $sql.$columns;
}
/**
* Prepares a JSON column being updated using the JSONB_SET function.
*
@@ -236,6 +380,114 @@ class PostgresGrammar extends Grammar
return "{$field} = jsonb_set({$field}::jsonb, {$path}, {$this->parameter($value)})";
}
/**
* Compile an update from statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileUpdateFrom(Builder $query, $values)
{
$table = $this->wrapTable($query->from);
// Each one of the columns in the update statements needs to be wrapped in the
// keyword identifiers, also a place-holder needs to be created for each of
// the values in the list of bindings so we can make the sets statements.
$columns = $this->compileUpdateColumns($query, $values);
$from = '';
if (isset($query->joins)) {
// When using Postgres, updates with joins list the joined tables in the from
// clause, which is different than other systems like MySQL. Here, we will
// compile out the tables that are joined and add them to a from clause.
$froms = collect($query->joins)->map(function ($join) {
return $this->wrapTable($join->table);
})->all();
if (count($froms) > 0) {
$from = ' from '.implode(', ', $froms);
}
}
$where = $this->compileUpdateWheres($query);
return trim("update {$table} set {$columns}{$from} {$where}");
}
/**
* Compile the additional where clauses for updates with joins.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileUpdateWheres(Builder $query)
{
$baseWheres = $this->compileWheres($query);
if (! isset($query->joins)) {
return $baseWheres;
}
// Once we compile the join constraints, we will either use them as the where
// clause or append them to the existing base where clauses. If we need to
// strip the leading boolean we will do so when using as the only where.
$joinWheres = $this->compileUpdateJoinWheres($query);
if (trim($baseWheres) == '') {
return 'where '.$this->removeLeadingBoolean($joinWheres);
}
return $baseWheres.' '.$joinWheres;
}
/**
* Compile the "join" clause where clauses for an update.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileUpdateJoinWheres(Builder $query)
{
$joinWheres = [];
// Here we will just loop through all of the join constraints and compile them
// all out then implode them. This should give us "where" like syntax after
// everything has been built and then we will join it to the real wheres.
foreach ($query->joins as $join) {
foreach ($join->wheres as $where) {
$method = "where{$where['type']}";
$joinWheres[] = $where['boolean'].' '.$this->$method($query, $where);
}
}
return implode(' ', $joinWheres);
}
/**
* Prepare the bindings for an update statement.
*
* @param array $bindings
* @param array $values
* @return array
*/
public function prepareBindingsForUpdateFrom(array $bindings, array $values)
{
$values = collect($values)->map(function ($value, $column) {
return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value))
? json_encode($value)
: $value;
})->all();
$bindingsWithoutWhere = Arr::except($bindings, ['select', 'where']);
return array_values(
array_merge($values, $bindings['where'], Arr::flatten($bindingsWithoutWhere))
);
}
/**
* Compile an update statement with joins or limit into SQL.
*
@@ -345,7 +597,7 @@ class PostgresGrammar extends Grammar
}
/**
*Wrap the given JSON selector for boolean values.
* Wrap the given JSON selector for boolean values.
*
* @param string $value
* @return string

View File

@@ -11,7 +11,7 @@ class SQLiteGrammar extends Grammar
/**
* All of the available clause operators.
*
* @var array
* @var string[]
*/
protected $operators = [
'=', '<', '>', '<=', '>=', '<>', '!=',
@@ -182,6 +182,30 @@ class SQLiteGrammar extends Grammar
})->implode(', ');
}
/**
* Compile an "upsert" statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param array $uniqueBy
* @param array $update
* @return string
*/
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
$sql = $this->compileInsert($query, $values);
$sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set ';
$columns = collect($update)->map(function ($value, $key) {
return is_numeric($key)
? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value)
: $this->wrap($key).' = '.$this->parameter($value);
})->implode(', ');
return $sql.$columns;
}
/**
* Group the nested JSON columns.
*

View File

@@ -11,7 +11,7 @@ class SqlServerGrammar extends Grammar
/**
* All of the available clause operators.
*
* @var array
* @var string[]
*/
protected $operators = [
'=', '<', '>', '<=', '>=', '!<', '!>', '<>', '!=',
@@ -31,15 +31,21 @@ class SqlServerGrammar extends Grammar
return parent::compileSelect($query);
}
// If an offset is present on the query, we will need to wrap the query in
// a big "ANSI" offset syntax block. This is very nasty compared to the
// other database systems but is necessary for implementing features.
if (is_null($query->columns)) {
$query->columns = ['*'];
}
$components = $this->compileComponents($query);
if (! empty($components['orders'])) {
return parent::compileSelect($query)." offset {$query->offset} rows fetch next {$query->limit} rows only";
}
// If an offset is present on the query, we will need to wrap the query in
// a big "ANSI" offset syntax block. This is very nasty compared to the
// other database systems but is necessary for implementing features.
return $this->compileAnsiOffset(
$query, $this->compileComponents($query)
$query, $components
);
}
@@ -61,8 +67,8 @@ class SqlServerGrammar extends Grammar
// If there is a limit on the query, but not an offset, we will add the top
// clause to the query, which serves as a "limit" type clause within the
// SQL Server system similar to the limit keywords available in MySQL.
if ($query->limit > 0 && $query->offset <= 0) {
$select .= 'top '.$query->limit.' ';
if (is_numeric($query->limit) && $query->limit > 0 && $query->offset <= 0) {
$select .= 'top '.((int) $query->limit).' ';
}
return $select.$this->columnize($columns);
@@ -90,6 +96,22 @@ class SqlServerGrammar extends Grammar
return $from;
}
/**
* {@inheritdoc}
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBitwise(Builder $query, $where)
{
$value = $this->parameter($where['value']);
$operator = str_replace('?', '??', $where['operator']);
return '('.$this->wrap($where['column']).' '.$operator.' '.$value.') != 0';
}
/**
* Compile a "where date" clause.
*
@@ -158,6 +180,36 @@ class SqlServerGrammar extends Grammar
return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value;
}
/**
* {@inheritdoc}
*
* @param array $having
* @return string
*/
protected function compileHaving(array $having)
{
if ($having['type'] === 'Bitwise') {
return $this->compileHavingBitwise($having);
}
return parent::compileHaving($having);
}
/**
* Compile a having clause involving a bitwise operator.
*
* @param array $having
* @return string
*/
protected function compileHavingBitwise($having)
{
$column = $this->wrap($having['column']);
$parameter = $this->parameter($having['value']);
return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.') != 0';
}
/**
* Create a full ANSI offset clause for the query.
*
@@ -181,6 +233,10 @@ class SqlServerGrammar extends Grammar
unset($components['orders']);
if ($this->queryOrderContainsSubquery($query)) {
$query->bindings = $this->sortBindingsForSubqueryOrderBy($query);
}
// Next we need to calculate the constraints that should be placed on the query
// to get the right offset and limit from our query but if there is no limit
// set we will just handle the offset only since that is all that matters.
@@ -200,6 +256,36 @@ class SqlServerGrammar extends Grammar
return ", row_number() over ({$orderings}) as row_num";
}
/**
* Determine if the query's order by clauses contain a subquery.
*
* @param \Illuminate\Database\Query\Builder $query
* @return bool
*/
protected function queryOrderContainsSubquery($query)
{
if (! is_array($query->orders)) {
return false;
}
return Arr::first($query->orders, function ($value) {
return $this->isExpression($value['column'] ?? null);
}, false) !== false;
}
/**
* Move the order bindings to be after the "select" statement to account for an order by subquery.
*
* @param \Illuminate\Database\Query\Builder $query
* @return array
*/
protected function sortBindingsForSubqueryOrderBy($query)
{
return Arr::sort($query->bindings, function ($bindings, $key) {
return array_search($key, ['select', 'order', 'from', 'join', 'where', 'groupBy', 'having', 'union', 'unionOrder']);
});
}
/**
* Compile a common table expression for a query.
*
@@ -222,10 +308,10 @@ class SqlServerGrammar extends Grammar
*/
protected function compileRowConstraint($query)
{
$start = $query->offset + 1;
$start = (int) $query->offset + 1;
if ($query->limit > 0) {
$finish = $query->offset + $query->limit;
$finish = (int) $query->offset + (int) $query->limit;
return "between {$start} and {$finish}";
}
@@ -341,6 +427,48 @@ class SqlServerGrammar extends Grammar
return "update {$alias} set {$columns} from {$table} {$joins} {$where}";
}
/**
* Compile an "upsert" statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param array $uniqueBy
* @param array $update
* @return string
*/
public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update)
{
$columns = $this->columnize(array_keys(reset($values)));
$sql = 'merge '.$this->wrapTable($query->from).' ';
$parameters = collect($values)->map(function ($record) {
return '('.$this->parameterize($record).')';
})->implode(', ');
$sql .= 'using (values '.$parameters.') '.$this->wrapTable('laravel_source').' ('.$columns.') ';
$on = collect($uniqueBy)->map(function ($column) use ($query) {
return $this->wrap('laravel_source.'.$column).' = '.$this->wrap($query->from.'.'.$column);
})->implode(' and ');
$sql .= 'on '.$on.' ';
if ($update) {
$update = collect($update)->map(function ($value, $key) {
return is_numeric($key)
? $this->wrap($value).' = '.$this->wrap('laravel_source.'.$value)
: $this->wrap($key).' = '.$this->parameter($value);
})->implode(', ');
$sql .= 'when matched then update set '.$update.' ';
}
$sql .= 'when not matched then insert ('.$columns.') values ('.$columns.');';
return $sql;
}
/**
* Prepare the bindings for an update statement.
*

View File

@@ -104,7 +104,7 @@ class JoinClause extends Builder
*
* @param \Closure|string $first
* @param string|null $operator
* @param string|null $second
* @param \Illuminate\Database\Query\Expression|string|null $second
* @return \Illuminate\Database\Query\JoinClause
*/
public function orOn($first, $operator = null, $second = null)

View File

14
vendor/laravel/framework/src/Illuminate/Database/README.md vendored Normal file → Executable file
View File

@@ -12,14 +12,14 @@ use Illuminate\Database\Capsule\Manager as Capsule;
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'database',
'username' => 'root',
'password' => 'password',
'charset' => 'utf8',
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'database',
'username' => 'root',
'password' => 'password',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'prefix' => '',
]);
// Set the event dispatcher used by Eloquent models... (optional)

21
vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php vendored Normal file → Executable file
View File

@@ -3,10 +3,14 @@
namespace Illuminate\Database;
use Doctrine\DBAL\Driver\PDOSqlite\Driver as DoctrineDriver;
use Doctrine\DBAL\Version;
use Illuminate\Database\PDO\SQLiteDriver;
use Illuminate\Database\Query\Grammars\SQLiteGrammar as QueryGrammar;
use Illuminate\Database\Query\Processors\SQLiteProcessor;
use Illuminate\Database\Schema\Grammars\SQLiteGrammar as SchemaGrammar;
use Illuminate\Database\Schema\SQLiteBuilder;
use Illuminate\Database\Schema\SqliteSchemaState;
use Illuminate\Filesystem\Filesystem;
class SQLiteConnection extends Connection
{
@@ -68,6 +72,19 @@ class SQLiteConnection extends Connection
return $this->withTablePrefix(new SchemaGrammar);
}
/**
* Get the schema state for the connection.
*
* @param \Illuminate\Filesystem\Filesystem|null $files
* @param callable|null $processFactory
*
* @throws \RuntimeException
*/
public function getSchemaState(Filesystem $files = null, callable $processFactory = null)
{
return new SqliteSchemaState($this, $files, $processFactory);
}
/**
* Get the default post processor instance.
*
@@ -81,11 +98,11 @@ class SQLiteConnection extends Connection
/**
* Get the Doctrine DBAL driver.
*
* @return \Doctrine\DBAL\Driver\PDOSqlite\Driver
* @return \Doctrine\DBAL\Driver\PDOSqlite\Driver|\Illuminate\Database\PDO\SQLiteDriver
*/
protected function getDoctrineDriver()
{
return new DoctrineDriver;
return class_exists(Version::class) ? new DoctrineDriver : new SQLiteDriver;
}
/**

196
vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php vendored Normal file → Executable file
View File

@@ -71,6 +71,13 @@ class Blueprint
*/
public $temporary = false;
/**
* The column to add new columns after.
*
* @var string
*/
public $after;
/**
* Create a new schema blueprint.
*
@@ -201,7 +208,7 @@ class Blueprint
protected function addFluentIndexes()
{
foreach ($this->columns as $column) {
foreach (['primary', 'unique', 'index', 'spatialIndex'] as $index) {
foreach (['primary', 'unique', 'index', 'fulltext', 'fullText', 'spatialIndex'] as $index) {
// If the index has been specified on the given column, but is simply equal
// to "true" (boolean), no name has been specified for this index so the
// index method can be called without a name and it will generate one.
@@ -255,7 +262,7 @@ class Blueprint
*
* @return bool
*/
protected function creating()
public function creating()
{
return collect($this->commands)->contains(function ($command) {
return $command->name === 'create';
@@ -360,6 +367,17 @@ class Blueprint
return $this->dropIndexCommand('dropIndex', 'index', $index);
}
/**
* Indicate that the given fulltext index should be dropped.
*
* @param string|array $index
* @return \Illuminate\Support\Fluent
*/
public function dropFullText($index)
{
return $this->dropIndexCommand('dropFullText', 'fulltext', $index);
}
/**
* Indicate that the given spatial index should be dropped.
*
@@ -382,6 +400,19 @@ class Blueprint
return $this->dropIndexCommand('dropForeign', 'foreign', $index);
}
/**
* Indicate that the given column and foreign key should be dropped.
*
* @param string $column
* @return \Illuminate\Support\Fluent
*/
public function dropConstrainedForeignId($column)
{
$this->dropForeign([$column]);
return $this->dropColumn($column);
}
/**
* Indicate that the given indexes should be renamed.
*
@@ -510,6 +541,19 @@ class Blueprint
return $this->indexCommand('index', $columns, $name, $algorithm);
}
/**
* Specify an fulltext for the table.
*
* @param string|array $columns
* @param string|null $name
* @param string|null $algorithm
* @return \Illuminate\Support\Fluent
*/
public function fullText($columns, $name = null, $algorithm = null)
{
return $this->indexCommand('fulltext', $columns, $name, $algorithm);
}
/**
* Specify a spatial index for the table.
*
@@ -657,6 +701,17 @@ class Blueprint
return $this->addColumn('string', $column, compact('length'));
}
/**
* Create a new tiny text column on the table.
*
* @param string $column
* @return \Illuminate\Database\Schema\ColumnDefinition
*/
public function tinyText($column)
{
return $this->addColumn('tinyText', $column);
}
/**
* Create a new text column on the table.
*
@@ -823,14 +878,30 @@ class Blueprint
*/
public function foreignId($column)
{
$this->columns[] = $column = new ForeignIdColumnDefinition($this, [
return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [
'type' => 'bigInteger',
'name' => $column,
'autoIncrement' => false,
'unsigned' => true,
]);
]));
}
return $column;
/**
* Create a foreign ID column for the given model.
*
* @param \Illuminate\Database\Eloquent\Model|string $model
* @param string|null $column
* @return \Illuminate\Database\Schema\ForeignIdColumnDefinition
*/
public function foreignIdFor($model, $column = null)
{
if (is_string($model)) {
$model = new $model;
}
return $model->getKeyType() === 'int' && $model->getIncrementing()
? $this->foreignId($column ?: $model->getForeignKey())
: $this->foreignUuid($column ?: $model->getForeignKey());
}
/**
@@ -1158,10 +1229,10 @@ class Blueprint
*/
public function foreignUuid($column)
{
return $this->columns[] = new ForeignIdColumnDefinition($this, [
return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [
'type' => 'uuid',
'name' => $column,
]);
]));
}
/**
@@ -1307,11 +1378,11 @@ class Blueprint
*/
public function morphs($name, $indexName = null)
{
$this->string("{$name}_type");
$this->unsignedBigInteger("{$name}_id");
$this->index(["{$name}_type", "{$name}_id"], $indexName);
if (Builder::$defaultMorphKeyType === 'uuid') {
$this->uuidMorphs($name, $indexName);
} else {
$this->numericMorphs($name, $indexName);
}
}
/**
@@ -1322,6 +1393,38 @@ class Blueprint
* @return void
*/
public function nullableMorphs($name, $indexName = null)
{
if (Builder::$defaultMorphKeyType === 'uuid') {
$this->nullableUuidMorphs($name, $indexName);
} else {
$this->nullableNumericMorphs($name, $indexName);
}
}
/**
* Add the proper columns for a polymorphic table using numeric IDs (incremental).
*
* @param string $name
* @param string|null $indexName
* @return void
*/
public function numericMorphs($name, $indexName = null)
{
$this->string("{$name}_type");
$this->unsignedBigInteger("{$name}_id");
$this->index(["{$name}_type", "{$name}_id"], $indexName);
}
/**
* Add nullable columns for a polymorphic table using numeric IDs (incremental).
*
* @param string $name
* @param string|null $indexName
* @return void
*/
public function nullableNumericMorphs($name, $indexName = null)
{
$this->string("{$name}_type")->nullable();
@@ -1441,11 +1544,44 @@ class Blueprint
*/
public function addColumn($type, $name, array $parameters = [])
{
$this->columns[] = $column = new ColumnDefinition(
return $this->addColumnDefinition(new ColumnDefinition(
array_merge(compact('type', 'name'), $parameters)
);
));
}
return $column;
/**
* Add a new column definition to the blueprint.
*
* @param \Illuminate\Database\Schema\ColumnDefinition $definition
* @return \Illuminate\Database\Schema\ColumnDefinition
*/
protected function addColumnDefinition($definition)
{
$this->columns[] = $definition;
if ($this->after) {
$definition->after($this->after);
$this->after = $definition->name;
}
return $definition;
}
/**
* Add the columns from the callback after the given column.
*
* @param string $column
* @param \Closure $callback
* @return void
*/
public function after($column, Closure $callback)
{
$this->after = $column;
$callback($this);
$this->after = null;
}
/**
@@ -1542,4 +1678,34 @@ class Blueprint
return (bool) $column->change;
});
}
/**
* Determine if the blueprint has auto-increment columns.
*
* @return bool
*/
public function hasAutoIncrementColumn()
{
return ! is_null(collect($this->getAddedColumns())->first(function ($column) {
return $column->autoIncrement === true;
}));
}
/**
* Get the auto-increment column starting values.
*
* @return array
*/
public function autoIncrementingStartingValues()
{
if (! $this->hasAutoIncrementColumn()) {
return [];
}
return collect($this->getAddedColumns())->mapWithKeys(function ($column) {
return $column->autoIncrement === true
? [$column->name => $column->get('startingValue', $column->get('from'))]
: [$column->name => null];
})->filter()->all();
}
}

98
vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php vendored Normal file → Executable file
View File

@@ -3,10 +3,10 @@
namespace Illuminate\Database\Schema;
use Closure;
use Doctrine\DBAL\Types\Type;
use Illuminate\Container\Container;
use Illuminate\Database\Connection;
use InvalidArgumentException;
use LogicException;
use RuntimeException;
class Builder
{
@@ -38,6 +38,13 @@ class Builder
*/
public static $defaultStringLength = 255;
/**
* The default relationship morph key type.
*
* @var string
*/
public static $defaultMorphKeyType = 'int';
/**
* Create a new database Schema manager.
*
@@ -61,6 +68,59 @@ class Builder
static::$defaultStringLength = $length;
}
/**
* Set the default morph key type for migrations.
*
* @param string $type
* @return void
*
* @throws \InvalidArgumentException
*/
public static function defaultMorphKeyType(string $type)
{
if (! in_array($type, ['int', 'uuid'])) {
throw new InvalidArgumentException("Morph key type must be 'int' or 'uuid'.");
}
static::$defaultMorphKeyType = $type;
}
/**
* Set the default morph key type for migrations to UUIDs.
*
* @return void
*/
public static function morphUsingUuids()
{
return static::defaultMorphKeyType('uuid');
}
/**
* Create a database in the schema.
*
* @param string $name
* @return bool
*
* @throws \LogicException
*/
public function createDatabase($name)
{
throw new LogicException('This database driver does not support creating databases.');
}
/**
* Drop a database from the schema if the database exists.
*
* @param string $name
* @return bool
*
* @throws \LogicException
*/
public function dropDatabaseIfExists($name)
{
throw new LogicException('This database driver does not support dropping databases.');
}
/**
* Determine if the given table exists.
*
@@ -193,6 +253,20 @@ class Builder
}));
}
/**
* Drop columns from a table schema.
*
* @param string $table
* @param string|array $columns
* @return void
*/
public function dropColumns($table, $columns)
{
$this->table($table, function (Blueprint $blueprint) use ($columns) {
$blueprint->dropColumn($columns);
});
}
/**
* Drop all tables from the database.
*
@@ -307,7 +381,7 @@ class Builder
return call_user_func($this->resolver, $table, $callback, $prefix);
}
return new Blueprint($table, $callback, $prefix);
return Container::getInstance()->make(Blueprint::class, compact('table', 'callback', 'prefix'));
}
/**
@@ -317,26 +391,10 @@ class Builder
* @param string $name
* @param string $type
* @return void
*
* @throws \Doctrine\DBAL\DBALException
* @throws \RuntimeException
*/
public function registerCustomDoctrineType($class, $name, $type)
{
if (! $this->connection->isDoctrineAvailable()) {
throw new RuntimeException(
'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
);
}
if (! Type::hasType($name)) {
Type::addType($name, $class);
$this->connection
->getDoctrineSchemaManager()
->getDatabasePlatform()
->registerDoctrineTypeMapping($type, $name);
}
$this->connection->registerDoctrineType($class, $name, $type);
}
/**

View File

@@ -2,7 +2,6 @@
namespace Illuminate\Database\Schema;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Fluent;
/**
@@ -12,21 +11,26 @@ use Illuminate\Support\Fluent;
* @method $this change() Change the column
* @method $this charset(string $charset) Specify a character set for the column (MySQL)
* @method $this collation(string $collation) Specify a collation for the column (MySQL/PostgreSQL/SQL Server)
* @method $this comment(string $comment) Add a comment to the column (MySQL)
* @method $this comment(string $comment) Add a comment to the column (MySQL/PostgreSQL)
* @method $this default(mixed $value) Specify a "default" value for the column
* @method $this first() Place the column "first" in the table (MySQL)
* @method $this from(int $startingValue) Set the starting value of an auto-incrementing field (MySQL / PostgreSQL)
* @method $this generatedAs(string|Expression $expression = null) Create a SQL compliant identity column (PostgreSQL)
* @method $this index(string $indexName = null) Add an index
* @method $this invisible() Specify that the column should be invisible to "SELECT *" (MySQL)
* @method $this nullable(bool $value = true) Allow NULL values to be inserted into the column
* @method $this persisted() Mark the computed generated column as persistent (SQL Server)
* @method $this primary() Add a primary index
* @method $this spatialIndex() Add a spatial index
* @method $this storedAs(string $expression) Create a stored generated column (MySQL)
* @method $this fulltext(string $indexName = null) Add a fulltext index
* @method $this spatialIndex(string $indexName = null) Add a spatial index
* @method $this startingValue(int $startingValue) Set the starting value of an auto-incrementing field (MySQL/PostgreSQL)
* @method $this storedAs(string $expression) Create a stored generated column (MySQL/PostgreSQL/SQLite)
* @method $this type(string $type) Specify a type for the column
* @method $this unique(string $indexName = null) Add a unique index
* @method $this unsigned() Set the INTEGER column as UNSIGNED (MySQL)
* @method $this useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value
* @method $this virtualAs(string $expression) Create a virtual generated column (MySQL)
* @method $this useCurrentOnUpdate() Set the TIMESTAMP column to use CURRENT_TIMESTAMP when updating (MySQL)
* @method $this virtualAs(string $expression) Create a virtual generated column (MySQL/PostgreSQL/SQLite)
*/
class ColumnDefinition extends Fluent
{

View File

@@ -32,7 +32,7 @@ class ForeignIdColumnDefinition extends ColumnDefinition
*
* @param string|null $table
* @param string $column
* @return \Illuminate\Support\Fluent|\Illuminate\Database\Schema\ForeignKeyDefinition
* @return \Illuminate\Database\Schema\ForeignKeyDefinition
*/
public function constrained($table = null, $column = 'id')
{
@@ -43,7 +43,7 @@ class ForeignIdColumnDefinition extends ColumnDefinition
* Specify which column this foreign ID references on another table.
*
* @param string $column
* @return \Illuminate\Support\Fluent|\Illuminate\Database\Schema\ForeignKeyDefinition
* @return \Illuminate\Database\Schema\ForeignKeyDefinition
*/
public function references($column)
{

View File

@@ -24,6 +24,16 @@ class ForeignKeyDefinition extends Fluent
return $this->onUpdate('cascade');
}
/**
* Indicate that updates should be restricted.
*
* @return $this
*/
public function restrictOnUpdate()
{
return $this->onUpdate('restrict');
}
/**
* Indicate that deletes should cascade.
*
@@ -34,6 +44,16 @@ class ForeignKeyDefinition extends Fluent
return $this->onDelete('cascade');
}
/**
* Indicate that deletes should be restricted.
*
* @return $this
*/
public function restrictOnDelete()
{
return $this->onDelete('restrict');
}
/**
* Indicate that deletes should set the foreign key value to null.
*

View File

@@ -198,6 +198,7 @@ class ChangeColumn
'binary',
'boolean',
'date',
'dateTime',
'decimal',
'double',
'float',

View File

@@ -9,6 +9,7 @@ use Illuminate\Database\Grammar as BaseGrammar;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Fluent;
use LogicException;
use RuntimeException;
abstract class Grammar extends BaseGrammar
@@ -27,6 +28,33 @@ abstract class Grammar extends BaseGrammar
*/
protected $fluentCommands = [];
/**
* Compile a create database command.
*
* @param string $name
* @param \Illuminate\Database\Connection $connection
* @return void
*
* @throws \LogicException
*/
public function compileCreateDatabase($name, $connection)
{
throw new LogicException('This database driver does not support creating databases.');
}
/**
* Compile a drop database if exists command.
*
* @param string $name
* @return void
*
* @throws \LogicException
*/
public function compileDropDatabaseIfExists($name)
{
throw new LogicException('This database driver does not support dropping databases.');
}
/**
* Compile a rename column command.
*
@@ -55,6 +83,32 @@ abstract class Grammar extends BaseGrammar
return ChangeColumn::compile($this, $blueprint, $command, $connection);
}
/**
* Compile a fulltext index key command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*
* @throws \RuntimeException
*/
public function compileFulltext(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('This database driver does not support fulltext index creation.');
}
/**
* Compile a drop fulltext index command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*/
public function compileDropFullText(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('This database driver does not support fulltext index creation.');
}
/**
* Compile a foreign key command.
*

Some files were not shown because too many files have changed in this diff Show More