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

202
vendor/illuminate/database/Capsule/Manager.php vendored Executable file
View File

@ -0,0 +1,202 @@
<?php
namespace Illuminate\Database\Capsule;
use Illuminate\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Support\Traits\CapsuleManagerTrait;
use PDO;
class Manager
{
use CapsuleManagerTrait;
/**
* The database manager instance.
*
* @var \Illuminate\Database\DatabaseManager
*/
protected $manager;
/**
* Create a new database capsule manager.
*
* @param \Illuminate\Container\Container|null $container
* @return void
*/
public function __construct(Container $container = null)
{
$this->setupContainer($container ?: new Container);
// Once we have the container setup, we will setup the default configuration
// options in the container "config" binding. This will make the database
// manager work correctly out of the box without extreme configuration.
$this->setupDefaultConfiguration();
$this->setupManager();
}
/**
* Setup the default database configuration options.
*
* @return void
*/
protected function setupDefaultConfiguration()
{
$this->container['config']['database.fetch'] = PDO::FETCH_OBJ;
$this->container['config']['database.default'] = 'default';
}
/**
* Build the database manager instance.
*
* @return void
*/
protected function setupManager()
{
$factory = new ConnectionFactory($this->container);
$this->manager = new DatabaseManager($this->container, $factory);
}
/**
* Get a connection instance from the global manager.
*
* @param string|null $connection
* @return \Illuminate\Database\Connection
*/
public static function connection($connection = null)
{
return static::$instance->getConnection($connection);
}
/**
* Get a fluent query builder instance.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
* @param string|null $as
* @param string|null $connection
* @return \Illuminate\Database\Query\Builder
*/
public static function table($table, $as = null, $connection = null)
{
return static::$instance->connection($connection)->table($table, $as);
}
/**
* Get a schema builder instance.
*
* @param string|null $connection
* @return \Illuminate\Database\Schema\Builder
*/
public static function schema($connection = null)
{
return static::$instance->connection($connection)->getSchemaBuilder();
}
/**
* Get a registered connection instance.
*
* @param string|null $name
* @return \Illuminate\Database\Connection
*/
public function getConnection($name = null)
{
return $this->manager->connection($name);
}
/**
* Register a connection with the manager.
*
* @param array $config
* @param string $name
* @return void
*/
public function addConnection(array $config, $name = 'default')
{
$connections = $this->container['config']['database.connections'];
$connections[$name] = $config;
$this->container['config']['database.connections'] = $connections;
}
/**
* Bootstrap Eloquent so it is ready for usage.
*
* @return void
*/
public function bootEloquent()
{
Eloquent::setConnectionResolver($this->manager);
// If we have an event dispatcher instance, we will go ahead and register it
// with the Eloquent ORM, allowing for model callbacks while creating and
// updating "model" instances; however, it is not necessary to operate.
if ($dispatcher = $this->getEventDispatcher()) {
Eloquent::setEventDispatcher($dispatcher);
}
}
/**
* Set the fetch mode for the database connections.
*
* @param int $fetchMode
* @return $this
*/
public function setFetchMode($fetchMode)
{
$this->container['config']['database.fetch'] = $fetchMode;
return $this;
}
/**
* Get the database manager instance.
*
* @return \Illuminate\Database\DatabaseManager
*/
public function getDatabaseManager()
{
return $this->manager;
}
/**
* Get the current event dispatcher instance.
*
* @return \Illuminate\Contracts\Events\Dispatcher|null
*/
public function getEventDispatcher()
{
if ($this->container->bound('events')) {
return $this->container['events'];
}
}
/**
* Set the event dispatcher instance to be used by connections.
*
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public function setEventDispatcher(Dispatcher $dispatcher)
{
$this->container->instance('events', $dispatcher);
}
/**
* Dynamically pass methods to the default connection.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
return static::connection()->$method(...$parameters);
}
}

View File

@ -0,0 +1,227 @@
<?php
namespace Illuminate\Database\Concerns;
use Illuminate\Container\Container;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
trait BuildsQueries
{
/**
* Chunk the results of the query.
*
* @param int $count
* @param callable $callback
* @return bool
*/
public function chunk($count, callable $callback)
{
$this->enforceOrderBy();
$page = 1;
do {
// We'll execute the query for the given page and get the results. If there are
// no results we can just break and return from here. When there are results
// we will call the callback with the current chunk of these results here.
$results = $this->forPage($page, $count)->get();
$countResults = $results->count();
if ($countResults == 0) {
break;
}
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
if ($callback($results, $page) === false) {
return false;
}
unset($results);
$page++;
} while ($countResults == $count);
return true;
}
/**
* Execute a callback over each item while chunking.
*
* @param callable $callback
* @param int $count
* @return bool
*/
public function each(callable $callback, $count = 1000)
{
return $this->chunk($count, function ($results) use ($callback) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
return false;
}
}
});
}
/**
* Chunk the results of a query by comparing IDs.
*
* @param int $count
* @param callable $callback
* @param string|null $column
* @param string|null $alias
* @return bool
*/
public function chunkById($count, callable $callback, $column = null, $alias = null)
{
$column = $column ?? $this->defaultKeyName();
$alias = $alias ?? $column;
$lastId = null;
do {
$clone = clone $this;
// We'll execute the query for the given page and get the results. If there are
// no results we can just break and return from here. When there are results
// we will call the callback with the current chunk of these results here.
$results = $clone->forPageAfterId($count, $lastId, $column)->get();
$countResults = $results->count();
if ($countResults == 0) {
break;
}
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
if ($callback($results) === false) {
return false;
}
$lastId = $results->last()->{$alias};
unset($results);
} while ($countResults == $count);
return true;
}
/**
* Execute a callback over each item while chunking by ID.
*
* @param callable $callback
* @param int $count
* @param string|null $column
* @param string|null $alias
* @return bool
*/
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
{
return $this->chunkById($count, function ($results) use ($callback) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
return false;
}
}
}, $column, $alias);
}
/**
* Execute the query and get the first result.
*
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model|object|static|null
*/
public function first($columns = ['*'])
{
return $this->take(1)->get($columns)->first();
}
/**
* Apply the callback's query changes if the given "value" is true.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
*/
public function when($value, $callback, $default = null)
{
if ($value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
}
return $this;
}
/**
* Pass the query to a given callback.
*
* @param callable $callback
* @return $this
*/
public function tap($callback)
{
return $this->when(true, $callback);
}
/**
* Apply the callback's query changes if the given "value" is false.
*
* @param mixed $value
* @param callable $callback
* @param callable|null $default
* @return mixed|$this
*/
public function unless($value, $callback, $default = null)
{
if (! $value) {
return $callback($this, $value) ?: $this;
} elseif ($default) {
return $default($this, $value) ?: $this;
}
return $this;
}
/**
* Create a new length-aware paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param int $total
* @param int $perPage
* @param int $currentPage
* @param array $options
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
protected function paginator($items, $total, $perPage, $currentPage, $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
'items', 'total', 'perPage', 'currentPage', 'options'
));
}
/**
* Create a new simple paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param int $perPage
* @param int $currentPage
* @param array $options
* @return \Illuminate\Pagination\Paginator
*/
protected function simplePaginator($items, $perPage, $currentPage, $options)
{
return Container::getInstance()->makeWith(Paginator::class, compact(
'items', 'perPage', 'currentPage', 'options'
));
}
}

View File

@ -0,0 +1,292 @@
<?php
namespace Illuminate\Database\Concerns;
use Closure;
use Throwable;
trait ManagesTransactions
{
/**
* Execute a Closure within a transaction.
*
* @param \Closure $callback
* @param int $attempts
* @return mixed
*
* @throws \Throwable
*/
public function transaction(Closure $callback, $attempts = 1)
{
for ($currentAttempt = 1; $currentAttempt <= $attempts; $currentAttempt++) {
$this->beginTransaction();
// We'll simply execute the given callback within a try / catch block and if we
// catch any exception we can rollback this transaction so that none of this
// gets actually persisted to a database or stored in a permanent fashion.
try {
$callbackResult = $callback($this);
}
// If we catch an exception we'll rollback this transaction and try again if we
// are not out of attempts. If we are out of attempts we will just throw the
// exception back out and let the developer handle an uncaught exceptions.
catch (Throwable $e) {
$this->handleTransactionException(
$e, $currentAttempt, $attempts
);
continue;
}
try {
if ($this->transactions == 1) {
$this->getPdo()->commit();
}
$this->transactions = max(0, $this->transactions - 1);
} catch (Throwable $e) {
$this->handleCommitTransactionException(
$e, $currentAttempt, $attempts
);
continue;
}
$this->fireConnectionEvent('committed');
return $callbackResult;
}
}
/**
* Handle an exception encountered when running a transacted statement.
*
* @param \Throwable $e
* @param int $currentAttempt
* @param int $maxAttempts
* @return void
*
* @throws \Throwable
*/
protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
{
// On a deadlock, MySQL rolls back the entire transaction so we can't just
// retry the query. We have to throw this exception all the way out and
// let the developer handle it in another way. We will decrement too.
if ($this->causedByConcurrencyError($e) &&
$this->transactions > 1) {
$this->transactions--;
throw $e;
}
// If there was an exception we will rollback this transaction and then we
// can check if we have exceeded the maximum attempt count for this and
// if we haven't we will return and try this query again in our loop.
$this->rollBack();
if ($this->causedByConcurrencyError($e) &&
$currentAttempt < $maxAttempts) {
return;
}
throw $e;
}
/**
* Start a new database transaction.
*
* @return void
*
* @throws \Throwable
*/
public function beginTransaction()
{
$this->createTransaction();
$this->transactions++;
$this->fireConnectionEvent('beganTransaction');
}
/**
* Create a transaction within the database.
*
* @return void
*
* @throws \Throwable
*/
protected function createTransaction()
{
if ($this->transactions == 0) {
$this->reconnectIfMissingConnection();
try {
$this->getPdo()->beginTransaction();
} catch (Throwable $e) {
$this->handleBeginTransactionException($e);
}
} elseif ($this->transactions >= 1 && $this->queryGrammar->supportsSavepoints()) {
$this->createSavepoint();
}
}
/**
* Create a save point within the database.
*
* @return void
*
* @throws \Throwable
*/
protected function createSavepoint()
{
$this->getPdo()->exec(
$this->queryGrammar->compileSavepoint('trans'.($this->transactions + 1))
);
}
/**
* Handle an exception from a transaction beginning.
*
* @param \Throwable $e
* @return void
*
* @throws \Throwable
*/
protected function handleBeginTransactionException(Throwable $e)
{
if ($this->causedByLostConnection($e)) {
$this->reconnect();
$this->getPdo()->beginTransaction();
} else {
throw $e;
}
}
/**
* Commit the active database transaction.
*
* @return void
*
* @throws \Throwable
*/
public function commit()
{
if ($this->transactions == 1) {
$this->getPdo()->commit();
}
$this->transactions = max(0, $this->transactions - 1);
$this->fireConnectionEvent('committed');
}
/**
* Handle an exception encountered when committing a transaction.
*
* @param \Throwable $e
* @param int $currentAttempt
* @param int $maxAttempts
* @return void
*
* @throws \Throwable
*/
protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts)
{
$this->transactions = max(0, $this->transactions - 1);
if ($this->causedByConcurrencyError($e) &&
$currentAttempt < $maxAttempts) {
return;
}
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
}
throw $e;
}
/**
* Rollback the active database transaction.
*
* @param int|null $toLevel
* @return void
*
* @throws \Throwable
*/
public function rollBack($toLevel = null)
{
// We allow developers to rollback to a certain transaction level. We will verify
// that this given transaction level is valid before attempting to rollback to
// that level. If it's not we will just return out and not attempt anything.
$toLevel = is_null($toLevel)
? $this->transactions - 1
: $toLevel;
if ($toLevel < 0 || $toLevel >= $this->transactions) {
return;
}
// Next, we will actually perform this rollback within this database and fire the
// rollback event. We will also set the current transaction level to the given
// level that was passed into this method so it will be right from here out.
try {
$this->performRollBack($toLevel);
} catch (Throwable $e) {
$this->handleRollBackException($e);
}
$this->transactions = $toLevel;
$this->fireConnectionEvent('rollingBack');
}
/**
* Perform a rollback within the database.
*
* @param int $toLevel
* @return void
*
* @throws \Throwable
*/
protected function performRollBack($toLevel)
{
if ($toLevel == 0) {
$this->getPdo()->rollBack();
} elseif ($this->queryGrammar->supportsSavepoints()) {
$this->getPdo()->exec(
$this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
);
}
}
/**
* Handle an exception from a rollback.
*
* @param \Throwable $e
* @return void
*
* @throws \Throwable
*/
protected function handleRollBackException(Throwable $e)
{
if ($this->causedByLostConnection($e)) {
$this->transactions = 0;
}
throw $e;
}
/**
* Get the number of active transactions.
*
* @return int
*/
public function transactionLevel()
{
return $this->transactions;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Illuminate\Database;
use Illuminate\Support\ConfigurationUrlParser as BaseConfigurationUrlParser;
class ConfigurationUrlParser extends BaseConfigurationUrlParser
{
//
}

1291
vendor/illuminate/database/Connection.php vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,163 @@
<?php
namespace Illuminate\Database;
use Closure;
interface ConnectionInterface
{
/**
* Begin a fluent query against a database table.
*
* @param \Closure|\Illuminate\Database\Query\Builder|string $table
* @param string|null $as
* @return \Illuminate\Database\Query\Builder
*/
public function table($table, $as = null);
/**
* Get a new raw query expression.
*
* @param mixed $value
* @return \Illuminate\Database\Query\Expression
*/
public function raw($value);
/**
* Run a select statement and return a single result.
*
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @return mixed
*/
public function selectOne($query, $bindings = [], $useReadPdo = true);
/**
* Run a select statement against the database.
*
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @return array
*/
public function select($query, $bindings = [], $useReadPdo = true);
/**
* Run a select statement against the database and returns a generator.
*
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @return \Generator
*/
public function cursor($query, $bindings = [], $useReadPdo = true);
/**
* Run an insert statement against the database.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function insert($query, $bindings = []);
/**
* Run an update statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function update($query, $bindings = []);
/**
* Run a delete statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function delete($query, $bindings = []);
/**
* Execute an SQL statement and return the boolean result.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function statement($query, $bindings = []);
/**
* Run an SQL statement and get the number of rows affected.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function affectingStatement($query, $bindings = []);
/**
* Run a raw, unprepared query against the PDO connection.
*
* @param string $query
* @return bool
*/
public function unprepared($query);
/**
* Prepare the query bindings for execution.
*
* @param array $bindings
* @return array
*/
public function prepareBindings(array $bindings);
/**
* Execute a Closure within a transaction.
*
* @param \Closure $callback
* @param int $attempts
* @return mixed
*
* @throws \Throwable
*/
public function transaction(Closure $callback, $attempts = 1);
/**
* Start a new database transaction.
*
* @return void
*/
public function beginTransaction();
/**
* Commit the active database transaction.
*
* @return void
*/
public function commit();
/**
* Rollback the active database transaction.
*
* @return void
*/
public function rollBack();
/**
* Get the number of active transactions.
*
* @return int
*/
public function transactionLevel();
/**
* Execute the given callback in "dry run" mode.
*
* @param \Closure $callback
* @return array
*/
public function pretend(Closure $callback);
}

View File

@ -0,0 +1,92 @@
<?php
namespace Illuminate\Database;
class ConnectionResolver implements ConnectionResolverInterface
{
/**
* All of the registered connections.
*
* @var array
*/
protected $connections = [];
/**
* The default connection name.
*
* @var string
*/
protected $default;
/**
* Create a new connection resolver instance.
*
* @param array $connections
* @return void
*/
public function __construct(array $connections = [])
{
foreach ($connections as $name => $connection) {
$this->addConnection($name, $connection);
}
}
/**
* Get a database connection instance.
*
* @param string|null $name
* @return \Illuminate\Database\ConnectionInterface
*/
public function connection($name = null)
{
if (is_null($name)) {
$name = $this->getDefaultConnection();
}
return $this->connections[$name];
}
/**
* Add a connection to the resolver.
*
* @param string $name
* @param \Illuminate\Database\ConnectionInterface $connection
* @return void
*/
public function addConnection($name, ConnectionInterface $connection)
{
$this->connections[$name] = $connection;
}
/**
* Check if a connection has been registered.
*
* @param string $name
* @return bool
*/
public function hasConnection($name)
{
return isset($this->connections[$name]);
}
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection()
{
return $this->default;
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name)
{
$this->default = $name;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Illuminate\Database;
interface ConnectionResolverInterface
{
/**
* Get a database connection instance.
*
* @param string|null $name
* @return \Illuminate\Database\ConnectionInterface
*/
public function connection($name = null);
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection();
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name);
}

View File

@ -0,0 +1,289 @@
<?php
namespace Illuminate\Database\Connectors;
use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Connection;
use Illuminate\Database\MySqlConnection;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\SQLiteConnection;
use Illuminate\Database\SqlServerConnection;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use PDOException;
class ConnectionFactory
{
/**
* The IoC container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* Create a new connection factory instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return void
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Establish a PDO connection based on the configuration.
*
* @param array $config
* @param string|null $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}
return $this->createSingleConnection($config);
}
/**
* Parse and prepare the database configuration.
*
* @param array $config
* @param string $name
* @return array
*/
protected function parseConfig(array $config, $name)
{
return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
}
/**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config);
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
/**
* Create a read / write database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createReadWriteConnection(array $config)
{
$connection = $this->createSingleConnection($this->getWriteConfig($config));
return $connection->setReadPdo($this->createReadPdo($config));
}
/**
* Create a new PDO instance for reading.
*
* @param array $config
* @return \Closure
*/
protected function createReadPdo(array $config)
{
return $this->createPdoResolver($this->getReadConfig($config));
}
/**
* Get the read configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getReadConfig(array $config)
{
return $this->mergeReadWriteConfig(
$config, $this->getReadWriteConfig($config, 'read')
);
}
/**
* Get the write configuration for a read / write connection.
*
* @param array $config
* @return array
*/
protected function getWriteConfig(array $config)
{
return $this->mergeReadWriteConfig(
$config, $this->getReadWriteConfig($config, 'write')
);
}
/**
* Get a read / write level configuration.
*
* @param array $config
* @param string $type
* @return array
*/
protected function getReadWriteConfig(array $config, $type)
{
return isset($config[$type][0])
? Arr::random($config[$type])
: $config[$type];
}
/**
* Merge a configuration for a read / write connection.
*
* @param array $config
* @param array $merge
* @return array
*/
protected function mergeReadWriteConfig(array $config, array $merge)
{
return Arr::except(array_merge($config, $merge), ['read', 'write']);
}
/**
* Create a new Closure that resolves to a PDO instance.
*
* @param array $config
* @return \Closure
*/
protected function createPdoResolver(array $config)
{
return array_key_exists('host', $config)
? $this->createPdoResolverWithHosts($config)
: $this->createPdoResolverWithoutHosts($config);
}
/**
* Create a new Closure that resolves to a PDO instance with a specific host or an array of hosts.
*
* @param array $config
* @return \Closure
*
* @throws \PDOException
*/
protected function createPdoResolverWithHosts(array $config)
{
return function () use ($config) {
foreach (Arr::shuffle($hosts = $this->parseHosts($config)) as $key => $host) {
$config['host'] = $host;
try {
return $this->createConnector($config)->connect($config);
} catch (PDOException $e) {
continue;
}
}
throw $e;
};
}
/**
* Parse the hosts configuration item into an array.
*
* @param array $config
* @return array
*
* @throws \InvalidArgumentException
*/
protected function parseHosts(array $config)
{
$hosts = Arr::wrap($config['host']);
if (empty($hosts)) {
throw new InvalidArgumentException('Database hosts array is empty.');
}
return $hosts;
}
/**
* Create a new Closure that resolves to a PDO instance where there is no configured host.
*
* @param array $config
* @return \Closure
*/
protected function createPdoResolverWithoutHosts(array $config)
{
return function () use ($config) {
return $this->createConnector($config)->connect($config);
};
}
/**
* Create a connector instance based on the configuration.
*
* @param array $config
* @return \Illuminate\Database\Connectors\ConnectorInterface
*
* @throws \InvalidArgumentException
*/
public function createConnector(array $config)
{
if (! isset($config['driver'])) {
throw new InvalidArgumentException('A driver must be specified.');
}
if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
return $this->container->make($key);
}
switch ($config['driver']) {
case 'mysql':
return new MySqlConnector;
case 'pgsql':
return new PostgresConnector;
case 'sqlite':
return new SQLiteConnector;
case 'sqlsrv':
return new SqlServerConnector;
}
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}].");
}
/**
* Create a new connection instance.
*
* @param string $driver
* @param \PDO|\Closure $connection
* @param string $database
* @param string $prefix
* @param array $config
* @return \Illuminate\Database\Connection
*
* @throws \InvalidArgumentException
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
}
switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new InvalidArgumentException("Unsupported driver [{$driver}].");
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace Illuminate\Database\Connectors;
use Doctrine\DBAL\Driver\PDOConnection;
use Exception;
use Illuminate\Database\DetectsLostConnections;
use PDO;
use Throwable;
class Connector
{
use DetectsLostConnections;
/**
* The default PDO connection options.
*
* @var array
*/
protected $options = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
];
/**
* Create a new PDO connection.
*
* @param string $dsn
* @param array $config
* @param array $options
* @return \PDO
*
* @throws \Exception
*/
public function createConnection($dsn, array $config, array $options)
{
[$username, $password] = [
$config['username'] ?? null, $config['password'] ?? null,
];
try {
return $this->createPdoConnection(
$dsn, $username, $password, $options
);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
/**
* Create a new PDO connection instance.
*
* @param string $dsn
* @param string $username
* @param string $password
* @param array $options
* @return \PDO
*/
protected function createPdoConnection($dsn, $username, $password, $options)
{
if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
return new PDOConnection($dsn, $username, $password, $options);
}
return new PDO($dsn, $username, $password, $options);
}
/**
* Determine if the connection is persistent.
*
* @param array $options
* @return bool
*/
protected function isPersistentConnection($options)
{
return isset($options[PDO::ATTR_PERSISTENT]) &&
$options[PDO::ATTR_PERSISTENT];
}
/**
* Handle an exception that occurred during connect execution.
*
* @param \Throwable $e
* @param string $dsn
* @param string $username
* @param string $password
* @param array $options
* @return \PDO
*
* @throws \Exception
*/
protected function tryAgainIfCausedByLostConnection(Throwable $e, $dsn, $username, $password, $options)
{
if ($this->causedByLostConnection($e)) {
return $this->createPdoConnection($dsn, $username, $password, $options);
}
throw $e;
}
/**
* Get the PDO options based on the configuration.
*
* @param array $config
* @return array
*/
public function getOptions(array $config)
{
$options = $config['options'] ?? [];
return array_diff_key($this->options, $options) + $options;
}
/**
* Get the default PDO connection options.
*
* @return array
*/
public function getDefaultOptions()
{
return $this->options;
}
/**
* Set the default PDO connection options.
*
* @param array $options
* @return void
*/
public function setDefaultOptions(array $options)
{
$this->options = $options;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Illuminate\Database\Connectors;
interface ConnectorInterface
{
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config);
}

View File

@ -0,0 +1,208 @@
<?php
namespace Illuminate\Database\Connectors;
use PDO;
class MySqlConnector extends Connector implements ConnectorInterface
{
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config)
{
$dsn = $this->getDsn($config);
$options = $this->getOptions($config);
// We need to grab the PDO options that should be used while making the brand
// new connection instance. The PDO options control various aspects of the
// connection's behavior, and some might be specified by the developers.
$connection = $this->createConnection($dsn, $config, $options);
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
}
$this->configureIsolationLevel($connection, $config);
$this->configureEncoding($connection, $config);
// Next, we will check to see if a timezone has been specified in this config
// and if it has we will issue a statement to modify the timezone with the
// database. Setting this DB timezone is an optional configuration item.
$this->configureTimezone($connection, $config);
$this->setModes($connection, $config);
return $connection;
}
/**
* Set the connection transaction isolation level.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureIsolationLevel($connection, array $config)
{
if (! isset($config['isolation_level'])) {
return;
}
$connection->prepare(
"SET SESSION TRANSACTION ISOLATION LEVEL {$config['isolation_level']}"
)->execute();
}
/**
* Set the connection character set and collation.
*
* @param \PDO $connection
* @param array $config
* @return void|\PDO
*/
protected function configureEncoding($connection, array $config)
{
if (! isset($config['charset'])) {
return $connection;
}
$connection->prepare(
"set names '{$config['charset']}'".$this->getCollation($config)
)->execute();
}
/**
* Get the collation for the configuration.
*
* @param array $config
* @return string
*/
protected function getCollation(array $config)
{
return isset($config['collation']) ? " collate '{$config['collation']}'" : '';
}
/**
* Set the timezone on the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureTimezone($connection, array $config)
{
if (isset($config['timezone'])) {
$connection->prepare('set time_zone="'.$config['timezone'].'"')->execute();
}
}
/**
* Create a DSN string from a configuration.
*
* Chooses socket or host/port based on the 'unix_socket' config value.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
return $this->hasSocket($config)
? $this->getSocketDsn($config)
: $this->getHostDsn($config);
}
/**
* Determine if the given configuration array has a UNIX socket value.
*
* @param array $config
* @return bool
*/
protected function hasSocket(array $config)
{
return isset($config['unix_socket']) && ! empty($config['unix_socket']);
}
/**
* Get the DSN string for a socket configuration.
*
* @param array $config
* @return string
*/
protected function getSocketDsn(array $config)
{
return "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
}
/**
* Get the DSN string for a host / port configuration.
*
* @param array $config
* @return string
*/
protected function getHostDsn(array $config)
{
extract($config, EXTR_SKIP);
return isset($port)
? "mysql:host={$host};port={$port};dbname={$database}"
: "mysql:host={$host};dbname={$database}";
}
/**
* Set the modes for the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function setModes(PDO $connection, array $config)
{
if (isset($config['modes'])) {
$this->setCustomModes($connection, $config);
} elseif (isset($config['strict'])) {
if ($config['strict']) {
$connection->prepare($this->strictMode($connection, $config))->execute();
} else {
$connection->prepare("set session sql_mode='NO_ENGINE_SUBSTITUTION'")->execute();
}
}
}
/**
* Set the custom modes on the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function setCustomModes(PDO $connection, array $config)
{
$modes = implode(',', $config['modes']);
$connection->prepare("set session sql_mode='{$modes}'")->execute();
}
/**
* Get the query to enable strict mode.
*
* @param \PDO $connection
* @param array $config
* @return string
*/
protected function strictMode(PDO $connection, $config)
{
$version = $config['version'] ?? $connection->getAttribute(PDO::ATTR_SERVER_VERSION);
if (version_compare($version, '8.0.11') >= 0) {
return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
}
return "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
}
}

View File

@ -0,0 +1,194 @@
<?php
namespace Illuminate\Database\Connectors;
use PDO;
class PostgresConnector extends Connector implements ConnectorInterface
{
/**
* The default PDO connection options.
*
* @var array
*/
protected $options = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
];
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config)
{
// First we'll create the basic DSN and connection instance connecting to the
// using the configuration option specified by the developer. We will also
// set the default character set on the connections to UTF-8 by default.
$connection = $this->createConnection(
$this->getDsn($config), $config, $this->getOptions($config)
);
$this->configureEncoding($connection, $config);
// Next, we will check to see if a timezone has been specified in this config
// and if it has we will issue a statement to modify the timezone with the
// database. Setting this DB timezone is an optional configuration item.
$this->configureTimezone($connection, $config);
$this->configureSchema($connection, $config);
// Postgres allows an application_name to be set by the user and this name is
// used to when monitoring the application with pg_stat_activity. So we'll
// determine if the option has been specified and run a statement if so.
$this->configureApplicationName($connection, $config);
$this->configureSynchronousCommit($connection, $config);
return $connection;
}
/**
* Set the connection character set and collation.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureEncoding($connection, $config)
{
if (! isset($config['charset'])) {
return;
}
$connection->prepare("set names '{$config['charset']}'")->execute();
}
/**
* Set the timezone on the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureTimezone($connection, array $config)
{
if (isset($config['timezone'])) {
$timezone = $config['timezone'];
$connection->prepare("set time zone '{$timezone}'")->execute();
}
}
/**
* Set the schema on the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureSchema($connection, $config)
{
if (isset($config['schema'])) {
$schema = $this->formatSchema($config['schema']);
$connection->prepare("set search_path to {$schema}")->execute();
}
}
/**
* Format the schema for the DSN.
*
* @param array|string $schema
* @return string
*/
protected function formatSchema($schema)
{
if (is_array($schema)) {
return '"'.implode('", "', $schema).'"';
}
return '"'.$schema.'"';
}
/**
* Set the schema on the connection.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureApplicationName($connection, $config)
{
if (isset($config['application_name'])) {
$applicationName = $config['application_name'];
$connection->prepare("set application_name to '$applicationName'")->execute();
}
}
/**
* Create a DSN string from a configuration.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
// First we will create the basic DSN setup as well as the port if it is in
// in the configuration options. This will give us the basic DSN we will
// need to establish the PDO connections and return them back for use.
extract($config, EXTR_SKIP);
$host = isset($host) ? "host={$host};" : '';
$dsn = "pgsql:{$host}dbname={$database}";
// If a port was specified, we will add it to this Postgres DSN connections
// format. Once we have done that we are ready to return this connection
// string back out for usage, as this has been fully constructed here.
if (isset($config['port'])) {
$dsn .= ";port={$port}";
}
return $this->addSslOptions($dsn, $config);
}
/**
* Add the SSL options to the DSN.
*
* @param string $dsn
* @param array $config
* @return string
*/
protected function addSslOptions($dsn, array $config)
{
foreach (['sslmode', 'sslcert', 'sslkey', 'sslrootcert'] as $option) {
if (isset($config[$option])) {
$dsn .= ";{$option}={$config[$option]}";
}
}
return $dsn;
}
/**
* Configure the synchronous_commit setting.
*
* @param \PDO $connection
* @param array $config
* @return void
*/
protected function configureSynchronousCommit($connection, array $config)
{
if (! isset($config['synchronous_commit'])) {
return;
}
$connection->prepare("set synchronous_commit to '{$config['synchronous_commit']}'")->execute();
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Illuminate\Database\Connectors;
use InvalidArgumentException;
class SQLiteConnector extends Connector implements ConnectorInterface
{
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*
* @throws \InvalidArgumentException
*/
public function connect(array $config)
{
$options = $this->getOptions($config);
// SQLite supports "in-memory" databases that only last as long as the owning
// connection does. These are useful for tests or for short lifetime store
// querying. In-memory databases may only have a single open connection.
if ($config['database'] === ':memory:') {
return $this->createConnection('sqlite::memory:', $config, $options);
}
$path = realpath($config['database']);
// Here we'll verify that the SQLite database exists before going any further
// as the developer probably wants to know if the database exists and this
// SQLite driver will not throw any exception if it does not by default.
if ($path === false) {
throw new InvalidArgumentException("Database ({$config['database']}) does not exist.");
}
return $this->createConnection("sqlite:{$path}", $config, $options);
}
}

View File

@ -0,0 +1,205 @@
<?php
namespace Illuminate\Database\Connectors;
use Illuminate\Support\Arr;
use PDO;
class SqlServerConnector extends Connector implements ConnectorInterface
{
/**
* The PDO connection options.
*
* @var array
*/
protected $options = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
];
/**
* Establish a database connection.
*
* @param array $config
* @return \PDO
*/
public function connect(array $config)
{
$options = $this->getOptions($config);
return $this->createConnection($this->getDsn($config), $config, $options);
}
/**
* Create a DSN string from a configuration.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
// First we will create the basic DSN setup as well as the port if it is in
// in the configuration options. This will give us the basic DSN we will
// need to establish the PDO connections and return them back for use.
if ($this->prefersOdbc($config)) {
return $this->getOdbcDsn($config);
}
if (in_array('sqlsrv', $this->getAvailableDrivers())) {
return $this->getSqlSrvDsn($config);
} else {
return $this->getDblibDsn($config);
}
}
/**
* Determine if the database configuration prefers ODBC.
*
* @param array $config
* @return bool
*/
protected function prefersOdbc(array $config)
{
return in_array('odbc', $this->getAvailableDrivers()) &&
($config['odbc'] ?? null) === true;
}
/**
* Get the DSN string for a DbLib connection.
*
* @param array $config
* @return string
*/
protected function getDblibDsn(array $config)
{
return $this->buildConnectString('dblib', array_merge([
'host' => $this->buildHostString($config, ':'),
'dbname' => $config['database'],
], Arr::only($config, ['appname', 'charset', 'version'])));
}
/**
* Get the DSN string for an ODBC connection.
*
* @param array $config
* @return string
*/
protected function getOdbcDsn(array $config)
{
return isset($config['odbc_datasource_name'])
? 'odbc:'.$config['odbc_datasource_name'] : '';
}
/**
* Get the DSN string for a SqlSrv connection.
*
* @param array $config
* @return string
*/
protected function getSqlSrvDsn(array $config)
{
$arguments = [
'Server' => $this->buildHostString($config, ','),
];
if (isset($config['database'])) {
$arguments['Database'] = $config['database'];
}
if (isset($config['readonly'])) {
$arguments['ApplicationIntent'] = 'ReadOnly';
}
if (isset($config['pooling']) && $config['pooling'] === false) {
$arguments['ConnectionPooling'] = '0';
}
if (isset($config['appname'])) {
$arguments['APP'] = $config['appname'];
}
if (isset($config['encrypt'])) {
$arguments['Encrypt'] = $config['encrypt'];
}
if (isset($config['trust_server_certificate'])) {
$arguments['TrustServerCertificate'] = $config['trust_server_certificate'];
}
if (isset($config['multiple_active_result_sets']) && $config['multiple_active_result_sets'] === false) {
$arguments['MultipleActiveResultSets'] = 'false';
}
if (isset($config['transaction_isolation'])) {
$arguments['TransactionIsolation'] = $config['transaction_isolation'];
}
if (isset($config['multi_subnet_failover'])) {
$arguments['MultiSubnetFailover'] = $config['multi_subnet_failover'];
}
if (isset($config['column_encryption'])) {
$arguments['ColumnEncryption'] = $config['column_encryption'];
}
if (isset($config['key_store_authentication'])) {
$arguments['KeyStoreAuthentication'] = $config['key_store_authentication'];
}
if (isset($config['key_store_principal_id'])) {
$arguments['KeyStorePrincipalId'] = $config['key_store_principal_id'];
}
if (isset($config['key_store_secret'])) {
$arguments['KeyStoreSecret'] = $config['key_store_secret'];
}
if (isset($config['login_timeout'])) {
$arguments['LoginTimeout'] = $config['login_timeout'];
}
return $this->buildConnectString('sqlsrv', $arguments);
}
/**
* Build a connection string from the given arguments.
*
* @param string $driver
* @param array $arguments
* @return string
*/
protected function buildConnectString($driver, array $arguments)
{
return $driver.':'.implode(';', array_map(function ($key) use ($arguments) {
return sprintf('%s=%s', $key, $arguments[$key]);
}, array_keys($arguments)));
}
/**
* Build a host string from the given configuration.
*
* @param array $config
* @param string $separator
* @return string
*/
protected function buildHostString(array $config, $separator)
{
if (empty($config['port'])) {
return $config['host'];
}
return $config['host'].$separator.$config['port'];
}
/**
* Get the available PDO drivers.
*
* @return array
*/
protected function getAvailableDrivers()
{
return PDO::getAvailableDrivers();
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace Illuminate\Database\Console\Factories;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class FactoryMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'make:factory';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new model factory';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Factory';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return $this->resolveStubPath('/stubs/factory.stub');
}
/**
* Resolve the fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveStubPath($stub)
{
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
? $customPath
: __DIR__.$stub;
}
/**
* Build the class with the given name.
*
* @param string $name
* @return string
*/
protected function buildClass($name)
{
$namespaceModel = $this->option('model')
? $this->qualifyClass($this->option('model'))
: trim($this->rootNamespace(), '\\').'\\Model';
$model = class_basename($namespaceModel);
$replace = [
'NamespacedDummyModel' => $namespaceModel,
'{{ namespacedModel }}' => $namespaceModel,
'{{namespacedModel}}' => $namespaceModel,
'DummyModel' => $model,
'{{ model }}' => $model,
'{{model}}' => $model,
];
return str_replace(
array_keys($replace), array_values($replace), parent::buildClass($name)
);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = str_replace(
['\\', '/'], '', $this->argument('name')
);
return $this->laravel->databasePath()."/factories/{$name}.php";
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['model', 'm', InputOption::VALUE_OPTIONAL, 'The name of the model'],
];
}
}

View File

@ -0,0 +1,12 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use Faker\Generator as Faker;
use {{ namespacedModel }};
$factory->define({{ model }}::class, function (Faker $faker) {
return [
//
];
});

View File

@ -0,0 +1,51 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
class BaseCommand extends Command
{
/**
* Get all of the migration paths.
*
* @return array
*/
protected function getMigrationPaths()
{
// Here, we will check to see if a path option has been defined. If it has we will
// use the path relative to the root of the installation folder so our database
// migrations may be run for any customized path from within the application.
if ($this->input->hasOption('path') && $this->option('path')) {
return collect($this->option('path'))->map(function ($path) {
return ! $this->usingRealPath()
? $this->laravel->basePath().'/'.$path
: $path;
})->all();
}
return array_merge(
$this->migrator->paths(), [$this->getMigrationPath()]
);
}
/**
* Determine if the given path(s) are pre-resolved "real" paths.
*
* @return bool
*/
protected function usingRealPath()
{
return $this->input->hasOption('realpath') && $this->option('realpath');
}
/**
* Get the path to the migration directory.
*
* @return string
*/
protected function getMigrationPath()
{
return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'migrations';
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Input\InputOption;
class FreshCommand extends Command
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:fresh';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Drop all tables and re-run all migrations';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$database = $this->input->getOption('database');
$this->call('db:wipe', array_filter([
'--database' => $database,
'--drop-views' => $this->option('drop-views'),
'--drop-types' => $this->option('drop-types'),
'--force' => true,
]));
$this->call('migrate', array_filter([
'--database' => $database,
'--path' => $this->input->getOption('path'),
'--realpath' => $this->input->getOption('realpath'),
'--force' => true,
'--step' => $this->option('step'),
]));
if ($this->needsSeeding()) {
$this->runSeeder($database);
}
return 0;
}
/**
* Determine if the developer has requested database seeding.
*
* @return bool
*/
protected function needsSeeding()
{
return $this->option('seed') || $this->option('seeder');
}
/**
* Run the database seeder command.
*
* @param string $database
* @return void
*/
protected function runSeeder($database)
{
$this->call('db:seed', array_filter([
'--database' => $database,
'--class' => $this->option('seeder') ?: 'DatabaseSeeder',
'--force' => true,
]));
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
['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

@ -0,0 +1,70 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create the migration repository';
/**
* The repository instance.
*
* @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
*/
protected $repository;
/**
* Create a new migration install command instance.
*
* @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
* @return void
*/
public function __construct(MigrationRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->repository->setSource($this->input->getOption('database'));
$this->repository->createRepository();
$this->info('Migration table created successfully.');
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
];
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Database\Migrations\Migrator;
class MigrateCommand extends BaseCommand
{
use ConfirmableTrait;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'migrate {--database= : The database connection to use}
{--force : Force the operation to run when in production}
{--path=* : The path(s) to the migrations files to be executed}
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
{--pretend : Dump the SQL queries that would be run}
{--seed : Indicates if the seed task should be re-run}
{--step : Force the migrations to be run so they can be rolled back individually}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the database migrations';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$this->migrator->usingConnection($this->option('database'), function () {
$this->prepareDatabase();
// Next, we will check to see if a path option has been defined. If it has
// we will use the path relative to the root of this installation folder
// so that migrations may be run for any path within the applications.
$this->migrator->setOutput($this->output)
->run($this->getMigrationPaths(), [
'pretend' => $this->option('pretend'),
'step' => $this->option('step'),
]);
// Finally, if the "seed" option has been given, we will re-run the database
// seed task to re-populate the database, which is convenient when adding
// a migration and a seed at the same time, as it is only this command.
if ($this->option('seed') && ! $this->option('pretend')) {
$this->call('db:seed', ['--force' => true]);
}
});
return 0;
}
/**
* Prepare the migration database for running.
*
* @return void
*/
protected function prepareDatabase()
{
if (! $this->migrator->repositoryExists()) {
$this->call('migrate:install', array_filter([
'--database' => $this->option('database'),
]));
}
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Database\Migrations\MigrationCreator;
use Illuminate\Support\Composer;
use Illuminate\Support\Str;
class MigrateMakeCommand extends BaseCommand
{
/**
* The console command signature.
*
* @var string
*/
protected $signature = 'make:migration {name : The name of the migration}
{--create= : The table to be created}
{--table= : The table to migrate}
{--path= : The location where the migration file should be created}
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
{--fullpath : Output the full path of the migration}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new migration file';
/**
* The migration creator instance.
*
* @var \Illuminate\Database\Migrations\MigrationCreator
*/
protected $creator;
/**
* The Composer instance.
*
* @var \Illuminate\Support\Composer
*/
protected $composer;
/**
* Create a new migration install command instance.
*
* @param \Illuminate\Database\Migrations\MigrationCreator $creator
* @param \Illuminate\Support\Composer $composer
* @return void
*/
public function __construct(MigrationCreator $creator, Composer $composer)
{
parent::__construct();
$this->creator = $creator;
$this->composer = $composer;
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
// It's possible for the developer to specify the tables to modify in this
// schema operation. The developer may also specify if this table needs
// to be freshly created so we can create the appropriate migrations.
$name = Str::snake(trim($this->input->getArgument('name')));
$table = $this->input->getOption('table');
$create = $this->input->getOption('create') ?: false;
// If no table was given as an option but a create option is given then we
// will use the "create" option as the table name. This allows the devs
// to pass a table name into this option as a short-cut for creating.
if (! $table && is_string($create)) {
$table = $create;
$create = true;
}
// Next, we will attempt to guess the table name if this the migration has
// "create" in the name. This will allow us to provide a convenient way
// of creating migrations that create new tables for the application.
if (! $table) {
[$table, $create] = TableGuesser::guess($name);
}
// Now we are ready to write the migration out to disk. Once we've written
// the migration out, we will dump-autoload for the entire framework to
// make sure that the migrations are registered by the class loaders.
$this->writeMigration($name, $table, $create);
$this->composer->dumpAutoloads();
}
/**
* Write the migration file to disk.
*
* @param string $name
* @param string $table
* @param bool $create
* @return string
*/
protected function writeMigration($name, $table, $create)
{
$file = $this->creator->create(
$name, $this->getMigrationPath(), $table, $create
);
if (! $this->option('fullpath')) {
$file = pathinfo($file, PATHINFO_FILENAME);
}
$this->line("<info>Created Migration:</info> {$file}");
}
/**
* Get migration path (either specified by '--path' option or default location).
*
* @return string
*/
protected function getMigrationPath()
{
if (! is_null($targetPath = $this->input->getOption('path'))) {
return ! $this->usingRealPath()
? $this->laravel->basePath().'/'.$targetPath
: $targetPath;
}
return parent::getMigrationPath();
}
/**
* 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

@ -0,0 +1,151 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Input\InputOption;
class RefreshCommand extends Command
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:refresh';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Reset and re-run all migrations';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
// Next we'll gather some of the options so that we can have the right options
// to pass to the commands. This includes options such as which database to
// use and the path to use for the migration. Then we'll run the command.
$database = $this->input->getOption('database');
$path = $this->input->getOption('path');
// If the "step" option is specified it means we only want to rollback a small
// number of migrations before migrating again. For example, the user might
// only rollback and remigrate the latest four migrations instead of all.
$step = $this->input->getOption('step') ?: 0;
if ($step > 0) {
$this->runRollback($database, $path, $step);
} else {
$this->runReset($database, $path);
}
// The refresh command is essentially just a brief aggregate of a few other of
// the migration commands and just provides a convenient wrapper to execute
// them in succession. We'll also see if we need to re-seed the database.
$this->call('migrate', array_filter([
'--database' => $database,
'--path' => $path,
'--realpath' => $this->input->getOption('realpath'),
'--force' => true,
]));
if ($this->needsSeeding()) {
$this->runSeeder($database);
}
return 0;
}
/**
* Run the rollback command.
*
* @param string $database
* @param string $path
* @param int $step
* @return void
*/
protected function runRollback($database, $path, $step)
{
$this->call('migrate:rollback', array_filter([
'--database' => $database,
'--path' => $path,
'--realpath' => $this->input->getOption('realpath'),
'--step' => $step,
'--force' => true,
]));
}
/**
* Run the reset command.
*
* @param string $database
* @param string $path
* @return void
*/
protected function runReset($database, $path)
{
$this->call('migrate:reset', array_filter([
'--database' => $database,
'--path' => $path,
'--realpath' => $this->input->getOption('realpath'),
'--force' => true,
]));
}
/**
* Determine if the developer has requested database seeding.
*
* @return bool
*/
protected function needsSeeding()
{
return $this->option('seed') || $this->option('seeder');
}
/**
* Run the database seeder command.
*
* @param string $database
* @return void
*/
protected function runSeeder($database)
{
$this->call('db:seed', array_filter([
'--database' => $database,
'--class' => $this->option('seeder') ?: 'DatabaseSeeder',
'--force' => true,
]));
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'],
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'],
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted & re-run'],
];
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Input\InputOption;
class ResetCommand extends BaseCommand
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:reset';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Rollback all database migrations';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
return $this->migrator->usingConnection($this->option('database'), function () {
// First, we'll make sure that the migration table actually exists before we
// start trying to rollback and re-run all of the migrations. If it's not
// present we'll just bail out with an info message for the developers.
if (! $this->migrator->repositoryExists()) {
return $this->comment('Migration table not found.');
}
$this->migrator->setOutput($this->output)->reset(
$this->getMigrationPaths(), $this->option('pretend')
);
});
return 0;
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'],
];
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Input\InputOption;
class RollbackCommand extends BaseCommand
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:rollback';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Rollback the last database migration';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$this->migrator->usingConnection($this->option('database'), function () {
$this->migrator->setOutput($this->output)->rollback(
$this->getMigrationPaths(), [
'pretend' => $this->option('pretend'),
'step' => (int) $this->option('step'),
]
);
});
return 0;
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'],
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted'],
];
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace Illuminate\Database\Console\Migrations;
use Illuminate\Database\Migrations\Migrator;
use Illuminate\Support\Collection;
use Symfony\Component\Console\Input\InputOption;
class StatusCommand extends BaseCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:status';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Show the status of each migration';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* @return int|null
*/
public function handle()
{
return $this->migrator->usingConnection($this->option('database'), function () {
if (! $this->migrator->repositoryExists()) {
$this->error('Migration table not found.');
return 1;
}
$ran = $this->migrator->getRepository()->getRan();
$batches = $this->migrator->getRepository()->getMigrationBatches();
if (count($migrations = $this->getStatusFor($ran, $batches)) > 0) {
$this->table(['Ran?', 'Migration', 'Batch'], $migrations);
} else {
$this->error('No migrations found');
}
});
}
/**
* Get the status for the given ran migrations.
*
* @param array $ran
* @param array $batches
* @return \Illuminate\Support\Collection
*/
protected function getStatusFor(array $ran, array $batches)
{
return Collection::make($this->getAllMigrationFiles())
->map(function ($migration) use ($ran, $batches) {
$migrationName = $this->migrator->getMigrationName($migration);
return in_array($migrationName, $ran)
? ['<info>Yes</info>', $migrationName, $batches[$migrationName]]
: ['<fg=red>No</fg=red>', $migrationName];
});
}
/**
* Get an array of all of the migration files.
*
* @return array
*/
protected function getAllMigrationFiles()
{
return $this->migrator->getMigrationFiles($this->getMigrationPaths());
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to use'],
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
];
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Database\Console\Migrations;
class TableGuesser
{
const CREATE_PATTERNS = [
'/^create_(\w+)_table$/',
'/^create_(\w+)$/',
];
const CHANGE_PATTERNS = [
'/_(to|from|in)_(\w+)_table$/',
'/_(to|from|in)_(\w+)$/',
];
/**
* Attempt to guess the table name and "creation" status of the given migration.
*
* @param string $migration
* @return array
*/
public static function guess($migration)
{
foreach (self::CREATE_PATTERNS as $pattern) {
if (preg_match($pattern, $migration, $matches)) {
return [$matches[1], $create = true];
}
}
foreach (self::CHANGE_PATTERNS as $pattern) {
if (preg_match($pattern, $migration, $matches)) {
return [$matches[2], $create = false];
}
}
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace Illuminate\Database\Console\Seeds;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Console\Input\InputOption;
class SeedCommand extends Command
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'db:seed';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seed the database with records';
/**
* The connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* Create a new database seed command instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @return void
*/
public function __construct(Resolver $resolver)
{
parent::__construct();
$this->resolver = $resolver;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$previousConnection = $this->resolver->getDefaultConnection();
$this->resolver->setDefaultConnection($this->getDatabase());
Model::unguarded(function () {
$this->getSeeder()->__invoke();
});
if ($previousConnection) {
$this->resolver->setDefaultConnection($previousConnection);
}
$this->info('Database seeding completed successfully.');
return 0;
}
/**
* Get a seeder instance from the container.
*
* @return \Illuminate\Database\Seeder
*/
protected function getSeeder()
{
$class = $this->laravel->make($this->input->getOption('class'));
return $class->setContainer($this->laravel)->setCommand($this);
}
/**
* Get the name of the database connection to use.
*
* @return string
*/
protected function getDatabase()
{
$database = $this->input->getOption('database');
return $database ?: $this->laravel['config']['database.default'];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'DatabaseSeeder'],
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
];
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace Illuminate\Database\Console\Seeds;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Composer;
class SeederMakeCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'make:seeder';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new seeder class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Seeder';
/**
* The Composer instance.
*
* @var \Illuminate\Support\Composer
*/
protected $composer;
/**
* Create a new command instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param \Illuminate\Support\Composer $composer
* @return void
*/
public function __construct(Filesystem $files, Composer $composer)
{
parent::__construct($files);
$this->composer = $composer;
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
parent::handle();
$this->composer->dumpAutoloads();
}
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return $this->resolveStubPath('/stubs/seeder.stub');
}
/**
* Resolve the fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveStubPath($stub)
{
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
? $customPath
: __DIR__.$stub;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
return $this->laravel->databasePath().'/seeds/'.$name.'.php';
}
/**
* Parse the class name and format according to the root namespace.
*
* @param string $name
* @return string
*/
protected function qualifyClass($name)
{
return $name;
}
}

View File

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Seeder;
class {{ class }} extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Illuminate\Database\Console;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Input\InputOption;
class WipeCommand extends Command
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'db:wipe';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Drop all tables, views, and types';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$database = $this->input->getOption('database');
if ($this->option('drop-views')) {
$this->dropAllViews($database);
$this->info('Dropped all views successfully.');
}
$this->dropAllTables($database);
$this->info('Dropped all tables successfully.');
if ($this->option('drop-types')) {
$this->dropAllTypes($database);
$this->info('Dropped all types successfully.');
}
return 0;
}
/**
* Drop all of the database tables.
*
* @param string $database
* @return void
*/
protected function dropAllTables($database)
{
$this->laravel['db']->connection($database)
->getSchemaBuilder()
->dropAllTables();
}
/**
* Drop all of the database views.
*
* @param string $database
* @return void
*/
protected function dropAllViews($database)
{
$this->laravel['db']->connection($database)
->getSchemaBuilder()
->dropAllViews();
}
/**
* Drop all of the database types.
*
* @param string $database
* @return void
*/
protected function dropAllTypes($database)
{
$this->laravel['db']->connection($database)
->getSchemaBuilder()
->dropAllTypes();
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
];
}
}

369
vendor/illuminate/database/DatabaseManager.php vendored Executable file
View File

@ -0,0 +1,369 @@
<?php
namespace Illuminate\Database;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Support\Arr;
use Illuminate\Support\ConfigurationUrlParser;
use Illuminate\Support\Str;
use InvalidArgumentException;
use PDO;
/**
* @mixin \Illuminate\Database\Connection
*/
class DatabaseManager implements ConnectionResolverInterface
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The database connection factory instance.
*
* @var \Illuminate\Database\Connectors\ConnectionFactory
*/
protected $factory;
/**
* The active connection instances.
*
* @var array
*/
protected $connections = [];
/**
* The custom connection resolvers.
*
* @var array
*/
protected $extensions = [];
/**
* The callback to be executed to reconnect to a database.
*
* @var callable
*/
protected $reconnector;
/**
* Create a new database manager instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Database\Connectors\ConnectionFactory $factory
* @return void
*/
public function __construct($app, ConnectionFactory $factory)
{
$this->app = $app;
$this->factory = $factory;
$this->reconnector = function ($connection) {
$this->reconnect($connection->getName());
};
}
/**
* Get a database connection instance.
*
* @param string|null $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
[$database, $type] = $this->parseConnectionName($name);
$name = $name ?: $database;
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if (! isset($this->connections[$name])) {
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
}
return $this->connections[$name];
}
/**
* Parse the connection into an array of the name and read / write type.
*
* @param string $name
* @return array
*/
protected function parseConnectionName($name)
{
$name = $name ?: $this->getDefaultConnection();
return Str::endsWith($name, ['::read', '::write'])
? explode('::', $name, 2) : [$name, null];
}
/**
* Make the database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
protected function makeConnection($name)
{
$config = $this->configuration($name);
// First we will check by the connection name to see if an extension has been
// registered specifically for that connection. If it has we will call the
// Closure and pass it the config allowing it to resolve the connection.
if (isset($this->extensions[$name])) {
return call_user_func($this->extensions[$name], $config, $name);
}
// Next we will check to see if an extension has been registered for a driver
// and will call the Closure if so, which allows us to have a more generic
// resolver for the drivers themselves which applies to all connections.
if (isset($this->extensions[$driver = $config['driver']])) {
return call_user_func($this->extensions[$driver], $config, $name);
}
return $this->factory->make($config, $name);
}
/**
* Get the configuration for a connection.
*
* @param string $name
* @return array
*
* @throws \InvalidArgumentException
*/
protected function configuration($name)
{
$name = $name ?: $this->getDefaultConnection();
// To get the database connection configuration, we will just pull each of the
// connection configurations and get the configurations for the given name.
// If the configuration doesn't exist, we'll throw an exception and bail.
$connections = $this->app['config']['database.connections'];
if (is_null($config = Arr::get($connections, $name))) {
throw new InvalidArgumentException("Database connection [{$name}] not configured.");
}
return (new ConfigurationUrlParser)
->parseConfiguration($config);
}
/**
* Prepare the database connection instance.
*
* @param \Illuminate\Database\Connection $connection
* @param string $type
* @return \Illuminate\Database\Connection
*/
protected function configure(Connection $connection, $type)
{
$connection = $this->setPdoForType($connection, $type);
// First we'll set the fetch mode and a few other dependencies of the database
// connection. This method basically just configures and prepares it to get
// used by the application. Once we're finished we'll return it back out.
if ($this->app->bound('events')) {
$connection->setEventDispatcher($this->app['events']);
}
// Here we'll set a reconnector callback. This reconnector can be any callable
// so we will set a Closure to reconnect from this manager with the name of
// the connection, which will allow us to reconnect from the connections.
$connection->setReconnector($this->reconnector);
return $connection;
}
/**
* Prepare the read / write mode for database connection instance.
*
* @param \Illuminate\Database\Connection $connection
* @param string|null $type
* @return \Illuminate\Database\Connection
*/
protected function setPdoForType(Connection $connection, $type = null)
{
if ($type === 'read') {
$connection->setPdo($connection->getReadPdo());
} elseif ($type === 'write') {
$connection->setReadPdo($connection->getPdo());
}
return $connection;
}
/**
* Disconnect from the given database and remove from local cache.
*
* @param string|null $name
* @return void
*/
public function purge($name = null)
{
$name = $name ?: $this->getDefaultConnection();
$this->disconnect($name);
unset($this->connections[$name]);
}
/**
* Disconnect from the given database.
*
* @param string|null $name
* @return void
*/
public function disconnect($name = null)
{
if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) {
$this->connections[$name]->disconnect();
}
}
/**
* Reconnect to the given database.
*
* @param string|null $name
* @return \Illuminate\Database\Connection
*/
public function reconnect($name = null)
{
$this->disconnect($name = $name ?: $this->getDefaultConnection());
if (! isset($this->connections[$name])) {
return $this->connection($name);
}
return $this->refreshPdoConnections($name);
}
/**
* Set the default database connection for the callback execution.
*
* @param string $name
* @param callable $callback
* @return mixed
*/
public function usingConnection($name, callable $callback)
{
$previousName = $this->getDefaultConnection();
$this->setDefaultConnection($name);
return tap($callback(), function () use ($previousName) {
$this->setDefaultConnection($previousName);
});
}
/**
* Refresh the PDO connections on a given connection.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
protected function refreshPdoConnections($name)
{
$fresh = $this->makeConnection($name);
return $this->connections[$name]
->setPdo($fresh->getRawPdo())
->setReadPdo($fresh->getRawReadPdo());
}
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection()
{
return $this->app['config']['database.default'];
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name)
{
$this->app['config']['database.default'] = $name;
}
/**
* Get all of the support drivers.
*
* @return array
*/
public function supportedDrivers()
{
return ['mysql', 'pgsql', 'sqlite', 'sqlsrv'];
}
/**
* Get all of the drivers that are actually available.
*
* @return array
*/
public function availableDrivers()
{
return array_intersect(
$this->supportedDrivers(),
str_replace('dblib', 'sqlsrv', PDO::getAvailableDrivers())
);
}
/**
* Register an extension connection resolver.
*
* @param string $name
* @param callable $resolver
* @return void
*/
public function extend($name, callable $resolver)
{
$this->extensions[$name] = $resolver;
}
/**
* Return all of the created connections.
*
* @return array
*/
public function getConnections()
{
return $this->connections;
}
/**
* Set the database reconnector callback.
*
* @param callable $reconnector
* @return void
*/
public function setReconnector(callable $reconnector)
{
$this->reconnector = $reconnector;
}
/**
* Dynamically pass methods to the default connection.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace Illuminate\Database;
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;
class DatabaseServiceProvider extends ServiceProvider
{
/**
* The array of resolved Faker instances.
*
* @var array
*/
protected static $fakers = [];
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
Model::clearBootedModels();
$this->registerConnectionServices();
$this->registerEloquentFactory();
$this->registerQueueableEntityResolver();
}
/**
* Register the primary database bindings.
*
* @return void
*/
protected function registerConnectionServices()
{
// The connection factory is used to create the actual connection instances on
// the database. We will inject the factory into the manager so that it may
// make the connections while they are actually needed and not of before.
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
// The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
});
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
}
/**
* Register the Eloquent factory instance in the container.
*
* @return void
*/
protected function registerEloquentFactory()
{
$this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
$locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
if (! isset(static::$fakers[$locale])) {
static::$fakers[$locale] = FakerFactory::create($locale);
}
static::$fakers[$locale]->unique(true);
return static::$fakers[$locale];
});
$this->app->singleton(EloquentFactory::class, function ($app) {
return EloquentFactory::construct(
$app->make(FakerGenerator::class), $this->app->databasePath('factories')
);
});
}
/**
* Register the queueable entity resolver implementation.
*
* @return void
*/
protected function registerQueueableEntityResolver()
{
$this->app->singleton(EntityResolver::class, function () {
return new QueueEntityResolver;
});
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Illuminate\Database;
use Illuminate\Support\Str;
use PDOException;
use Throwable;
trait DetectsConcurrencyErrors
{
/**
* Determine if the given exception was caused by a concurrency error such as a deadlock or serialization failure.
*
* @param \Throwable $e
* @return bool
*/
protected function causedByConcurrencyError(Throwable $e)
{
if ($e instanceof PDOException && $e->getCode() === '40001') {
return true;
}
$message = $e->getMessage();
return Str::contains($message, [
'Deadlock found when trying to get lock',
'deadlock detected',
'The database file is locked',
'database is locked',
'database table is locked',
'A table in the database is locked',
'has been chosen as the deadlock victim',
'Lock wait timeout exceeded; try restarting transaction',
'WSREP detected deadlock/conflict and aborted the transaction. Try restarting the transaction',
]);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Illuminate\Database;
use Illuminate\Support\Str;
use Throwable;
trait DetectsLostConnections
{
/**
* Determine if the given exception was caused by a lost connection.
*
* @param \Throwable $e
* @return bool
*/
protected function causedByLostConnection(Throwable $e)
{
$message = $e->getMessage();
return Str::contains($message, [
'server has gone away',
'no connection to the server',
'Lost connection',
'is dead or not enabled',
'Error while sending',
'decryption failed or bad record mac',
'server closed the connection unexpectedly',
'SSL connection has been closed unexpectedly',
'Error writing data to the connection',
'Resource deadlock avoided',
'Transaction() on null',
'child connection forced to terminate due to client_idle_limit',
'query_wait_timeout',
'reset by peer',
'Physical connection is not usable',
'TCP Provider: Error code 0x68',
'ORA-03114',
'Packets out of order. Expected',
'Adaptive Server connection failed',
'Communication link failure',
'connection is no longer usable',
'Login timeout expired',
'SQLSTATE[HY000] [2002] Connection refused',
'running with the --read-only option so it cannot execute this statement',
'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
'SQLSTATE[HY000] [2002] Connection timed out',
'SSL: Connection timed out',
'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
'Temporary failure in name resolution',
]);
}
}

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,250 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Support\Str;
trait GuardsAttributes
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [];
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = ['*'];
/**
* Indicates if all mass assignment is enabled.
*
* @var bool
*/
protected static $unguarded = false;
/**
* The actual columns that exist on the database and can be guarded.
*
* @var array
*/
protected static $guardableColumns = [];
/**
* Get the fillable attributes for the model.
*
* @return array
*/
public function getFillable()
{
return $this->fillable;
}
/**
* Set the fillable attributes for the model.
*
* @param array $fillable
* @return $this
*/
public function fillable(array $fillable)
{
$this->fillable = $fillable;
return $this;
}
/**
* Merge new fillable attributes with existing fillable attributes on the model.
*
* @param array $fillable
* @return $this
*/
public function mergeFillable(array $fillable)
{
$this->fillable = array_merge($this->fillable, $fillable);
return $this;
}
/**
* Get the guarded attributes for the model.
*
* @return array
*/
public function getGuarded()
{
return $this->guarded;
}
/**
* Set the guarded attributes for the model.
*
* @param array $guarded
* @return $this
*/
public function guard(array $guarded)
{
$this->guarded = $guarded;
return $this;
}
/**
* Merge new guarded attributes with existing guarded attributes on the model.
*
* @param array $guarded
* @return $this
*/
public function mergeGuarded(array $guarded)
{
$this->guarded = array_merge($this->guarded, $guarded);
return $this;
}
/**
* Disable all mass assignable restrictions.
*
* @param bool $state
* @return void
*/
public static function unguard($state = true)
{
static::$unguarded = $state;
}
/**
* Enable the mass assignment restrictions.
*
* @return void
*/
public static function reguard()
{
static::$unguarded = false;
}
/**
* Determine if current state is "unguarded".
*
* @return bool
*/
public static function isUnguarded()
{
return static::$unguarded;
}
/**
* Run the given callable while being unguarded.
*
* @param callable $callback
* @return mixed
*/
public static function unguarded(callable $callback)
{
if (static::$unguarded) {
return $callback();
}
static::unguard();
try {
return $callback();
} finally {
static::reguard();
}
}
/**
* Determine if the given attribute may be mass assigned.
*
* @param string $key
* @return bool
*/
public function isFillable($key)
{
if (static::$unguarded) {
return true;
}
// If the key is in the "fillable" array, we can of course assume that it's
// a fillable attribute. Otherwise, we will check the guarded array when
// we need to determine if the attribute is black-listed on the model.
if (in_array($key, $this->getFillable())) {
return true;
}
// If the attribute is explicitly listed in the "guarded" array then we can
// return false immediately. This means this attribute is definitely not
// fillable and there is no point in going any further in this method.
if ($this->isGuarded($key)) {
return false;
}
return empty($this->getFillable()) &&
strpos($key, '.') === false &&
! Str::startsWith($key, '_');
}
/**
* Determine if the given key is guarded.
*
* @param string $key
* @return bool
*/
public function isGuarded($key)
{
if (empty($this->getGuarded())) {
return false;
}
return $this->getGuarded() == ['*'] ||
! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) ||
! $this->isGuardableColumn($key);
}
/**
* Determine if the given column is a valid, guardable column.
*
* @param string $key
* @return bool
*/
protected function isGuardableColumn($key)
{
if (! isset(static::$guardableColumns[get_class($this)])) {
static::$guardableColumns[get_class($this)] = $this->getConnection()
->getSchemaBuilder()
->getColumnListing($this->getTable());
}
return in_array($key, static::$guardableColumns[get_class($this)]);
}
/**
* Determine if the model is totally guarded.
*
* @return bool
*/
public function totallyGuarded()
{
return count($this->getFillable()) === 0 && $this->getGuarded() == ['*'];
}
/**
* Get the fillable attributes of a given array.
*
* @param array $attributes
* @return array
*/
protected function fillableFromArray(array $attributes)
{
if (count($this->getFillable()) > 0 && ! static::$unguarded) {
return array_intersect_key($attributes, array_flip($this->getFillable()));
}
return $attributes;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,415 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Events\NullDispatcher;
use Illuminate\Support\Arr;
use InvalidArgumentException;
trait HasEvents
{
/**
* The event map for the model.
*
* Allows for object-based events for native Eloquent events.
*
* @var array
*/
protected $dispatchesEvents = [];
/**
* User exposed observable events.
*
* These are extra user-defined events observers may subscribe to.
*
* @var array
*/
protected $observables = [];
/**
* Register observers with the model.
*
* @param object|array|string $classes
* @return void
*
* @throws \RuntimeException
*/
public static function observe($classes)
{
$instance = new static;
foreach (Arr::wrap($classes) as $class) {
$instance->registerObserver($class);
}
}
/**
* Register a single observer with the model.
*
* @param object|string $class
* @return void
*
* @throws \RuntimeException
*/
protected function registerObserver($class)
{
$className = $this->resolveObserverClassName($class);
// When registering a model observer, we will spin through the possible events
// and determine if this observer has that method. If it does, we will hook
// it into the model's event system, making it convenient to watch these.
foreach ($this->getObservableEvents() as $event) {
if (method_exists($class, $event)) {
static::registerModelEvent($event, $className.'@'.$event);
}
}
}
/**
* Resolve the observer's class name from an object or string.
*
* @param object|string $class
* @return string
*
* @throws \InvalidArgumentException
*/
private function resolveObserverClassName($class)
{
if (is_object($class)) {
return get_class($class);
}
if (class_exists($class)) {
return $class;
}
throw new InvalidArgumentException('Unable to find observer: '.$class);
}
/**
* Get the observable event names.
*
* @return array
*/
public function getObservableEvents()
{
return array_merge(
[
'retrieved', 'creating', 'created', 'updating', 'updated',
'saving', 'saved', 'restoring', 'restored', 'replicating',
'deleting', 'deleted', 'forceDeleted',
],
$this->observables
);
}
/**
* Set the observable event names.
*
* @param array $observables
* @return $this
*/
public function setObservableEvents(array $observables)
{
$this->observables = $observables;
return $this;
}
/**
* Add an observable event name.
*
* @param array|mixed $observables
* @return void
*/
public function addObservableEvents($observables)
{
$this->observables = array_unique(array_merge(
$this->observables, is_array($observables) ? $observables : func_get_args()
));
}
/**
* Remove an observable event name.
*
* @param array|mixed $observables
* @return void
*/
public function removeObservableEvents($observables)
{
$this->observables = array_diff(
$this->observables, is_array($observables) ? $observables : func_get_args()
);
}
/**
* Register a model event with the dispatcher.
*
* @param string $event
* @param \Closure|string $callback
* @return void
*/
protected static function registerModelEvent($event, $callback)
{
if (isset(static::$dispatcher)) {
$name = static::class;
static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
}
}
/**
* Fire the given event for the model.
*
* @param string $event
* @param bool $halt
* @return mixed
*/
protected function fireModelEvent($event, $halt = true)
{
if (! isset(static::$dispatcher)) {
return true;
}
// First, we will get the proper method to call on the event dispatcher, and then we
// will attempt to fire a custom, object based event for the given event. If that
// returns a result we can return that result, or we'll call the string events.
$method = $halt ? 'until' : 'dispatch';
$result = $this->filterModelEventResults(
$this->fireCustomModelEvent($event, $method)
);
if ($result === false) {
return false;
}
return ! empty($result) ? $result : static::$dispatcher->{$method}(
"eloquent.{$event}: ".static::class, $this
);
}
/**
* Fire a custom model event for the given event.
*
* @param string $event
* @param string $method
* @return mixed|null
*/
protected function fireCustomModelEvent($event, $method)
{
if (! isset($this->dispatchesEvents[$event])) {
return;
}
$result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));
if (! is_null($result)) {
return $result;
}
}
/**
* Filter the model event results.
*
* @param mixed $result
* @return mixed
*/
protected function filterModelEventResults($result)
{
if (is_array($result)) {
$result = array_filter($result, function ($response) {
return ! is_null($response);
});
}
return $result;
}
/**
* Register a retrieved model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function retrieved($callback)
{
static::registerModelEvent('retrieved', $callback);
}
/**
* Register a saving model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function saving($callback)
{
static::registerModelEvent('saving', $callback);
}
/**
* Register a saved model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function saved($callback)
{
static::registerModelEvent('saved', $callback);
}
/**
* Register an updating model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function updating($callback)
{
static::registerModelEvent('updating', $callback);
}
/**
* Register an updated model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function updated($callback)
{
static::registerModelEvent('updated', $callback);
}
/**
* Register a creating model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function creating($callback)
{
static::registerModelEvent('creating', $callback);
}
/**
* Register a created model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function created($callback)
{
static::registerModelEvent('created', $callback);
}
/**
* Register a replicating model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function replicating($callback)
{
static::registerModelEvent('replicating', $callback);
}
/**
* Register a deleting model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function deleting($callback)
{
static::registerModelEvent('deleting', $callback);
}
/**
* Register a deleted model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function deleted($callback)
{
static::registerModelEvent('deleted', $callback);
}
/**
* Remove all of the event listeners for the model.
*
* @return void
*/
public static function flushEventListeners()
{
if (! isset(static::$dispatcher)) {
return;
}
$instance = new static;
foreach ($instance->getObservableEvents() as $event) {
static::$dispatcher->forget("eloquent.{$event}: ".static::class);
}
foreach (array_values($instance->dispatchesEvents) as $event) {
static::$dispatcher->forget($event);
}
}
/**
* Get the event dispatcher instance.
*
* @return \Illuminate\Contracts\Events\Dispatcher
*/
public static function getEventDispatcher()
{
return static::$dispatcher;
}
/**
* Set the event dispatcher instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public static function setEventDispatcher(Dispatcher $dispatcher)
{
static::$dispatcher = $dispatcher;
}
/**
* Unset the event dispatcher for models.
*
* @return void
*/
public static function unsetEventDispatcher()
{
static::$dispatcher = null;
}
/**
* Execute a callback without firing any model events for any model type.
*
* @param callable $callback
* @return mixed
*/
public static function withoutEvents(callable $callback)
{
$dispatcher = static::getEventDispatcher();
if ($dispatcher) {
static::setEventDispatcher(new NullDispatcher($dispatcher));
}
try {
return $callback();
} finally {
if ($dispatcher) {
static::setEventDispatcher($dispatcher);
}
}
}
}

View File

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

View File

@ -0,0 +1,870 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
trait HasRelationships
{
/**
* The loaded relationships for the model.
*
* @var array
*/
protected $relations = [];
/**
* The relationships that should be touched on save.
*
* @var array
*/
protected $touches = [];
/**
* The many to many relationship methods.
*
* @var array
*/
public static $manyMethods = [
'belongsToMany', 'morphToMany', 'morphedByMany',
];
/**
* The relation resolver callbacks.
*
* @var array
*/
protected static $relationResolvers = [];
/**
* Define a dynamic relation resolver.
*
* @param string $name
* @param \Closure $callback
* @return void
*/
public static function resolveRelationUsing($name, Closure $callback)
{
static::$relationResolvers = array_replace_recursive(
static::$relationResolvers,
[static::class => [$name => $callback]]
);
}
/**
* Define a one-to-one relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOne($related, $foreignKey = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
$foreignKey = $foreignKey ?: $this->getForeignKey();
$localKey = $localKey ?: $this->getKeyName();
return $this->newHasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
/**
* Instantiate a new HasOne relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
{
return new HasOne($query, $parent, $foreignKey, $localKey);
}
/**
* Define a has-one-through relationship.
*
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|null $secondKey
* @param string|null $localKey
* @param string|null $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
public function hasOneThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
{
$through = new $through;
$firstKey = $firstKey ?: $this->getForeignKey();
$secondKey = $secondKey ?: $through->getForeignKey();
return $this->newHasOneThrough(
$this->newRelatedInstance($related)->newQuery(), $this, $through,
$firstKey, $secondKey, $localKey ?: $this->getKeyName(),
$secondLocalKey ?: $through->getKeyName()
);
}
/**
* Instantiate a new HasOneThrough relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $farParent
* @param \Illuminate\Database\Eloquent\Model $throughParent
* @param string $firstKey
* @param string $secondKey
* @param string $localKey
* @param string $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
{
return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
}
/**
* Define a polymorphic one-to-one relationship.
*
* @param string $related
* @param string $name
* @param string|null $type
* @param string|null $id
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
[$type, $id] = $this->getMorphs($name, $type, $id);
$table = $instance->getTable();
$localKey = $localKey ?: $this->getKeyName();
return $this->newMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}
/**
* Instantiate a new MorphOne relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey)
{
return new MorphOne($query, $parent, $type, $id, $localKey);
}
/**
* Define an inverse one-to-one or many relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $ownerKey
* @param string|null $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if (is_null($relation)) {
$relation = $this->guessBelongsToRelation();
}
$instance = $this->newRelatedInstance($related);
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if (is_null($foreignKey)) {
$foreignKey = Str::snake($relation).'_'.$instance->getKeyName();
}
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$ownerKey = $ownerKey ?: $instance->getKeyName();
return $this->newBelongsTo(
$instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
);
}
/**
* Instantiate a new BelongsTo relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $child
* @param string $foreignKey
* @param string $ownerKey
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
{
return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation);
}
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string|null $name
* @param string|null $type
* @param string|null $id
* @param string|null $ownerKey
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null, $ownerKey = null)
{
// If no name is provided, we will use the backtrace to get the function name
// since that is most likely the name of the polymorphic interface. We can
// use that to get both the class and foreign key that will be utilized.
$name = $name ?: $this->guessBelongsToRelation();
[$type, $id] = $this->getMorphs(
Str::snake($name), $type, $id
);
// If the type value is null it is probably safe to assume we're eager loading
// the relationship. In this case we'll just pass in a dummy query where we
// need to remove any eager loads that may already be defined on a model.
return is_null($class = $this->{$type}) || $class === ''
? $this->morphEagerTo($name, $type, $id, $ownerKey)
: $this->morphInstanceTo($class, $name, $type, $id, $ownerKey);
}
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string $name
* @param string $type
* @param string $id
* @param string $ownerKey
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
protected function morphEagerTo($name, $type, $id, $ownerKey)
{
return $this->newMorphTo(
$this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name
);
}
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string $target
* @param string $name
* @param string $type
* @param string $id
* @param string $ownerKey
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
protected function morphInstanceTo($target, $name, $type, $id, $ownerKey)
{
$instance = $this->newRelatedInstance(
static::getActualClassNameForMorph($target)
);
return $this->newMorphTo(
$instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name
);
}
/**
* Instantiate a new MorphTo relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $ownerKey
* @param string $type
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
{
return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation);
}
/**
* Retrieve the actual class name for a given morph class.
*
* @param string $class
* @return string
*/
public static function getActualClassNameForMorph($class)
{
return Arr::get(Relation::morphMap() ?: [], $class, $class);
}
/**
* Guess the "belongs to" relationship name.
*
* @return string
*/
protected function guessBelongsToRelation()
{
[$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
return $caller['function'];
}
/**
* Define a one-to-many relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasMany($related, $foreignKey = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
$foreignKey = $foreignKey ?: $this->getForeignKey();
$localKey = $localKey ?: $this->getKeyName();
return $this->newHasMany(
$instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
);
}
/**
* Instantiate a new HasMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
{
return new HasMany($query, $parent, $foreignKey, $localKey);
}
/**
* Define a has-many-through relationship.
*
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|null $secondKey
* @param string|null $localKey
* @param string|null $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
{
$through = new $through;
$firstKey = $firstKey ?: $this->getForeignKey();
$secondKey = $secondKey ?: $through->getForeignKey();
return $this->newHasManyThrough(
$this->newRelatedInstance($related)->newQuery(),
$this,
$through,
$firstKey,
$secondKey,
$localKey ?: $this->getKeyName(),
$secondLocalKey ?: $through->getKeyName()
);
}
/**
* Instantiate a new HasManyThrough relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $farParent
* @param \Illuminate\Database\Eloquent\Model $throughParent
* @param string $firstKey
* @param string $secondKey
* @param string $localKey
* @param string $secondLocalKey
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
{
return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
}
/**
* Define a polymorphic one-to-many relationship.
*
* @param string $related
* @param string $name
* @param string|null $type
* @param string|null $id
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
// Here we will gather up the morph type and ID for the relationship so that we
// can properly query the intermediate table of a relation. Finally, we will
// get the table and create the relationship instances for the developers.
[$type, $id] = $this->getMorphs($name, $type, $id);
$table = $instance->getTable();
$localKey = $localKey ?: $this->getKeyName();
return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}
/**
* Instantiate a new MorphMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
{
return new MorphMany($query, $parent, $type, $id, $localKey);
}
/**
* Define a many-to-many relationship.
*
* @param string $related
* @param string|null $table
* @param string|null $foreignPivotKey
* @param string|null $relatedPivotKey
* @param string|null $parentKey
* @param string|null $relatedKey
* @param string|null $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
$parentKey = null, $relatedKey = null, $relation = null)
{
// If no relationship name was passed, we will pull backtraces to get the
// name of the calling function. We will use that function name as the
// title of this relation since that is a great convention to apply.
if (is_null($relation)) {
$relation = $this->guessBelongsToManyRelation();
}
// First, we'll need to determine the foreign key and "other key" for the
// relationship. Once we have determined the keys we'll make the query
// instances as well as the relationship instances we need for this.
$instance = $this->newRelatedInstance($related);
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
// If no table name was provided, we can guess it by concatenating the two
// models using underscores in alphabetical order. The two model names
// are transformed to snake case from their default CamelCase also.
if (is_null($table)) {
$table = $this->joiningTable($related, $instance);
}
return $this->newBelongsToMany(
$instance->newQuery(), $this, $table, $foreignPivotKey,
$relatedPivotKey, $parentKey ?: $this->getKeyName(),
$relatedKey ?: $instance->getKeyName(), $relation
);
}
/**
* Instantiate a new BelongsToMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
* @param string $relatedKey
* @param string|null $relationName
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey,
$parentKey, $relatedKey, $relationName = null)
{
return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName);
}
/**
* Define a polymorphic many-to-many relationship.
*
* @param string $related
* @param string $name
* @param string|null $table
* @param string|null $foreignPivotKey
* @param string|null $relatedPivotKey
* @param string|null $parentKey
* @param string|null $relatedKey
* @param bool $inverse
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphToMany($related, $name, $table = null, $foreignPivotKey = null,
$relatedPivotKey = null, $parentKey = null,
$relatedKey = null, $inverse = false)
{
$caller = $this->guessBelongsToManyRelation();
// First, we will need to determine the foreign key and "other key" for the
// relationship. Once we have determined the keys we will make the query
// instances, as well as the relationship instances we need for these.
$instance = $this->newRelatedInstance($related);
$foreignPivotKey = $foreignPivotKey ?: $name.'_id';
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
// Now we're ready to create a new query builder for this related model and
// the relationship instances for this relation. This relations will set
// appropriate query constraints then entirely manages the hydrations.
if (! $table) {
$words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
$lastWord = array_pop($words);
$table = implode('', $words).Str::plural($lastWord);
}
return $this->newMorphToMany(
$instance->newQuery(), $this, $name, $table,
$foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(),
$relatedKey ?: $instance->getKeyName(), $caller, $inverse
);
}
/**
* Instantiate a new MorphToMany relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $name
* @param string $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
* @param string $relatedKey
* @param string|null $relationName
* @param bool $inverse
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey,
$relatedPivotKey, $parentKey, $relatedKey,
$relationName = null, $inverse = false)
{
return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey,
$relationName, $inverse);
}
/**
* Define a polymorphic, inverse many-to-many relationship.
*
* @param string $related
* @param string $name
* @param string|null $table
* @param string|null $foreignPivotKey
* @param string|null $relatedPivotKey
* @param string|null $parentKey
* @param string|null $relatedKey
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
$relatedPivotKey = null, $parentKey = null, $relatedKey = null)
{
$foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
// For the inverse of the polymorphic many-to-many relations, we will change
// the way we determine the foreign and other keys, as it is the opposite
// of the morph-to-many method since we're figuring out these inverses.
$relatedPivotKey = $relatedPivotKey ?: $name.'_id';
return $this->morphToMany(
$related, $name, $table, $foreignPivotKey,
$relatedPivotKey, $parentKey, $relatedKey, true
);
}
/**
* Get the relationship name of the belongsToMany relationship.
*
* @return string|null
*/
protected function guessBelongsToManyRelation()
{
$caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
return ! in_array(
$trace['function'],
array_merge(static::$manyMethods, ['guessBelongsToManyRelation'])
);
});
return ! is_null($caller) ? $caller['function'] : null;
}
/**
* Get the joining table name for a many-to-many relation.
*
* @param string $related
* @param \Illuminate\Database\Eloquent\Model|null $instance
* @return string
*/
public function joiningTable($related, $instance = null)
{
// The joining table name, by convention, is simply the snake cased models
// sorted alphabetically and concatenated with an underscore, so we can
// just sort the models and join them together to get the table name.
$segments = [
$instance ? $instance->joiningTableSegment()
: Str::snake(class_basename($related)),
$this->joiningTableSegment(),
];
// Now that we have the model names in an array we can just sort them and
// use the implode function to join them together with an underscores,
// which is typically used by convention within the database system.
sort($segments);
return strtolower(implode('_', $segments));
}
/**
* Get this model's half of the intermediate table name for belongsToMany relationships.
*
* @return string
*/
public function joiningTableSegment()
{
return Str::snake(class_basename($this));
}
/**
* Determine if the model touches a given relation.
*
* @param string $relation
* @return bool
*/
public function touches($relation)
{
return in_array($relation, $this->getTouchedRelations());
}
/**
* Touch the owning relations of the model.
*
* @return void
*/
public function touchOwners()
{
foreach ($this->getTouchedRelations() as $relation) {
$this->$relation()->touch();
if ($this->$relation instanceof self) {
$this->$relation->fireModelEvent('saved', false);
$this->$relation->touchOwners();
} elseif ($this->$relation instanceof Collection) {
$this->$relation->each->touchOwners();
}
}
}
/**
* Get the polymorphic relationship columns.
*
* @param string $name
* @param string $type
* @param string $id
* @return array
*/
protected function getMorphs($name, $type, $id)
{
return [$type ?: $name.'_type', $id ?: $name.'_id'];
}
/**
* Get the class name for polymorphic relations.
*
* @return string
*/
public function getMorphClass()
{
$morphMap = Relation::morphMap();
if (! empty($morphMap) && in_array(static::class, $morphMap)) {
return array_search(static::class, $morphMap, true);
}
return static::class;
}
/**
* Create a new model instance for a related model.
*
* @param string $class
* @return mixed
*/
protected function newRelatedInstance($class)
{
return tap(new $class, function ($instance) {
if (! $instance->getConnectionName()) {
$instance->setConnection($this->connection);
}
});
}
/**
* Get all the loaded relations for the instance.
*
* @return array
*/
public function getRelations()
{
return $this->relations;
}
/**
* Get a specified relationship.
*
* @param string $relation
* @return mixed
*/
public function getRelation($relation)
{
return $this->relations[$relation];
}
/**
* Determine if the given relation is loaded.
*
* @param string $key
* @return bool
*/
public function relationLoaded($key)
{
return array_key_exists($key, $this->relations);
}
/**
* Set the given relationship on the model.
*
* @param string $relation
* @param mixed $value
* @return $this
*/
public function setRelation($relation, $value)
{
$this->relations[$relation] = $value;
return $this;
}
/**
* Unset a loaded relationship.
*
* @param string $relation
* @return $this
*/
public function unsetRelation($relation)
{
unset($this->relations[$relation]);
return $this;
}
/**
* Set the entire relations array on the model.
*
* @param array $relations
* @return $this
*/
public function setRelations(array $relations)
{
$this->relations = $relations;
return $this;
}
/**
* Duplicate the instance and unset all the loaded relations.
*
* @return $this
*/
public function withoutRelations()
{
$model = clone $this;
return $model->unsetRelations();
}
/**
* Unset all the loaded relations for the instance.
*
* @return $this
*/
public function unsetRelations()
{
$this->relations = [];
return $this;
}
/**
* Get the relationships that are touched on save.
*
* @return array
*/
public function getTouchedRelations()
{
return $this->touches;
}
/**
* Set the relationships that are touched on save.
*
* @param array $touches
* @return $this
*/
public function setTouchedRelations(array $touches)
{
$this->touches = $touches;
return $this;
}
}

View File

@ -0,0 +1,149 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Illuminate\Support\Facades\Date;
trait HasTimestamps
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
/**
* Update the model's update timestamp.
*
* @return bool
*/
public function touch()
{
if (! $this->usesTimestamps()) {
return false;
}
$this->updateTimestamps();
return $this->save();
}
/**
* Update the creation and update timestamps.
*
* @return void
*/
protected function updateTimestamps()
{
$time = $this->freshTimestamp();
$updatedAtColumn = $this->getUpdatedAtColumn();
if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) {
$this->setUpdatedAt($time);
}
$createdAtColumn = $this->getCreatedAtColumn();
if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) {
$this->setCreatedAt($time);
}
}
/**
* Set the value of the "created at" attribute.
*
* @param mixed $value
* @return $this
*/
public function setCreatedAt($value)
{
$this->{$this->getCreatedAtColumn()} = $value;
return $this;
}
/**
* Set the value of the "updated at" attribute.
*
* @param mixed $value
* @return $this
*/
public function setUpdatedAt($value)
{
$this->{$this->getUpdatedAtColumn()} = $value;
return $this;
}
/**
* Get a fresh timestamp for the model.
*
* @return \Illuminate\Support\Carbon
*/
public function freshTimestamp()
{
return Date::now();
}
/**
* Get a fresh timestamp for the model.
*
* @return string
*/
public function freshTimestampString()
{
return $this->fromDateTime($this->freshTimestamp());
}
/**
* Determine if the model uses timestamps.
*
* @return bool
*/
public function usesTimestamps()
{
return $this->timestamps;
}
/**
* Get the name of the "created at" column.
*
* @return string|null
*/
public function getCreatedAtColumn()
{
return static::CREATED_AT;
}
/**
* Get the name of the "updated at" column.
*
* @return string|null
*/
public function getUpdatedAtColumn()
{
return static::UPDATED_AT;
}
/**
* Get the fully qualified "created at" column.
*
* @return string
*/
public function getQualifiedCreatedAtColumn()
{
return $this->qualifyColumn($this->getCreatedAtColumn());
}
/**
* Get the fully qualified "updated at" column.
*
* @return string
*/
public function getQualifiedUpdatedAtColumn()
{
return $this->qualifyColumn($this->getUpdatedAtColumn());
}
}

View File

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

View File

@ -0,0 +1,498 @@
<?php
namespace Illuminate\Database\Eloquent\Concerns;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Str;
use RuntimeException;
trait QueriesRelationships
{
/**
* Add a relationship count / exists condition to the query.
*
* @param \Illuminate\Database\Eloquent\Relations\Relation|string $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*
* @throws \RuntimeException
*/
public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
if (is_string($relation)) {
if (strpos($relation, '.') !== false) {
return $this->hasNested($relation, $operator, $count, $boolean, $callback);
}
$relation = $this->getRelationWithoutConstraints($relation);
}
if ($relation instanceof MorphTo) {
throw new RuntimeException('Please use whereHasMorph() for MorphTo relationships.');
}
// If we only need to check for the existence of the relation, then we can optimize
// the subquery to only run a "where exists" clause instead of this full "count"
// clause. This will make these queries run much faster compared with a count.
$method = $this->canUseExistsForExistenceCheck($operator, $count)
? 'getRelationExistenceQuery'
: 'getRelationExistenceCountQuery';
$hasQuery = $relation->{$method}(
$relation->getRelated()->newQueryWithoutRelationships(), $this
);
// Next we will call any given callback as an "anonymous" scope so they can get the
// proper logical grouping of the where clauses if needed by this Eloquent query
// builder. Then, we will be ready to finalize and return this query instance.
if ($callback) {
$hasQuery->callScope($callback);
}
return $this->addHasWhere(
$hasQuery, $relation, $operator, $count, $boolean
);
}
/**
* Add nested relationship count / exists conditions to the query.
*
* Sets up recursive call to whereHas until we finish the nested relation.
*
* @param string $relations
* @param string $operator
* @param int $count
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
{
$relations = explode('.', $relations);
$doesntHave = $operator === '<' && $count === 1;
if ($doesntHave) {
$operator = '>=';
$count = 1;
}
$closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
// In order to nest "has", we need to add count relation constraints on the
// callback Closure. We'll do this by simply passing the Closure its own
// reference to itself so it calls itself recursively on each segment.
count($relations) > 1
? $q->whereHas(array_shift($relations), $closure)
: $q->has(array_shift($relations), $operator, $count, 'and', $callback);
};
return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure);
}
/**
* Add a relationship count / exists condition to the query with an "or".
*
* @param string $relation
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orHas($relation, $operator = '>=', $count = 1)
{
return $this->has($relation, $operator, $count, 'or');
}
/**
* Add a relationship count / exists condition to the query.
*
* @param string $relation
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function doesntHave($relation, $boolean = 'and', Closure $callback = null)
{
return $this->has($relation, '<', 1, $boolean, $callback);
}
/**
* Add a relationship count / exists condition to the query with an "or".
*
* @param string $relation
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orDoesntHave($relation)
{
return $this->doesntHave($relation, 'or');
}
/**
* Add a relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
{
return $this->has($relation, $operator, $count, 'and', $callback);
}
/**
* Add a relationship count / exists condition to the query with where clauses and an "or".
*
* @param string $relation
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
{
return $this->has($relation, $operator, $count, 'or', $callback);
}
/**
* Add a relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function whereDoesntHave($relation, Closure $callback = null)
{
return $this->doesntHave($relation, 'and', $callback);
}
/**
* Add a relationship count / exists condition to the query with where clauses and an "or".
*
* @param string $relation
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereDoesntHave($relation, Closure $callback = null)
{
return $this->doesntHave($relation, 'or', $callback);
}
/**
* Add a polymorphic relationship count / exists condition to the query.
*
* @param string $relation
* @param string|array $types
* @param string $operator
* @param int $count
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
$relation = $this->getRelationWithoutConstraints($relation);
$types = (array) $types;
if ($types === ['*']) {
$types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();
}
foreach ($types as &$type) {
$type = Relation::getMorphedModel($type) ?? $type;
}
return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) {
foreach ($types as $type) {
$query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) {
$belongsTo = $this->getBelongsToRelation($relation, $type);
if ($callback) {
$callback = function ($query) use ($callback, $type) {
return $callback($query, $type);
};
}
$query->where($this->query->from.'.'.$relation->getMorphType(), '=', (new $type)->getMorphClass())
->whereHas($belongsTo, $callback, $operator, $count);
});
}
}, null, null, $boolean);
}
/**
* Get the BelongsTo relationship for a single polymorphic type.
*
* @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation
* @param string $type
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
protected function getBelongsToRelation(MorphTo $relation, $type)
{
$belongsTo = Relation::noConstraints(function () use ($relation, $type) {
return $this->model->belongsTo(
$type,
$relation->getForeignKeyName(),
$relation->getOwnerKeyName()
);
});
$belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());
return $belongsTo;
}
/**
* Add a polymorphic relationship count / exists condition to the query with an "or".
*
* @param string $relation
* @param string|array $types
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orHasMorph($relation, $types, $operator = '>=', $count = 1)
{
return $this->hasMorph($relation, $types, $operator, $count, 'or');
}
/**
* Add a polymorphic relationship count / exists condition to the query.
*
* @param string $relation
* @param string|array $types
* @param string $boolean
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function doesntHaveMorph($relation, $types, $boolean = 'and', Closure $callback = null)
{
return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback);
}
/**
* Add a polymorphic relationship count / exists condition to the query with an "or".
*
* @param string $relation
* @param string|array $types
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orDoesntHaveMorph($relation, $types)
{
return $this->doesntHaveMorph($relation, $types, 'or');
}
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param string|array $types
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
{
return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback);
}
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
*
* @param string $relation
* @param string|array $types
* @param \Closure|null $callback
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
{
return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback);
}
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses.
*
* @param string $relation
* @param string|array $types
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function whereDoesntHaveMorph($relation, $types, Closure $callback = null)
{
return $this->doesntHaveMorph($relation, $types, 'and', $callback);
}
/**
* Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
*
* @param string $relation
* @param string|array $types
* @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null)
{
return $this->doesntHaveMorph($relation, $types, 'or', $callback);
}
/**
* Add subselect queries to count the relations.
*
* @param mixed $relations
* @return $this
*/
public function withCount($relations)
{
if (empty($relations)) {
return $this;
}
if (is_null($this->query->columns)) {
$this->query->select([$this->query->from.'.*']);
}
$relations = is_array($relations) ? $relations : func_get_args();
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
// First we will determine if the name has been aliased using an "as" clause on the name
// and if it has we will extract the actual relationship name and the desired name of
// the resulting column. This allows multiple counts on the same relationship name.
$segments = explode(' ', $name);
unset($alias);
if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
[$name, $alias] = [$segments[0], $segments[2]];
}
$relation = $this->getRelationWithoutConstraints($name);
// Here we will get the relationship count query and prepare to add it to the main query
// as a sub-select. First, we'll get the "has" query and use that to get the relation
// count query. We will normalize the relation name then append _count as the name.
$query = $relation->getRelationExistenceCountQuery(
$relation->getRelated()->newQuery(), $this
);
$query->callScope($constraints);
$query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
$query->orders = null;
$query->setBindings([], 'order');
if (count($query->columns) > 1) {
$query->columns = [$query->columns[0]];
$query->bindings['select'] = [];
}
// Finally we will add the proper result column alias to the query and run the subselect
// statement against the query builder. Then we will return the builder instance back
// to the developer for further constraint chaining that needs to take place on it.
$column = $alias ?? Str::snake($name.'_count');
$this->selectSub($query, $column);
}
return $this;
}
/**
* Add the "has" condition where clause to the query.
*
* @param \Illuminate\Database\Eloquent\Builder $hasQuery
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @return \Illuminate\Database\Eloquent\Builder|static
*/
protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean)
{
$hasQuery->mergeConstraintsFrom($relation->getQuery());
return $this->canUseExistsForExistenceCheck($operator, $count)
? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1)
: $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
}
/**
* Merge the where constraints from another query to the current query.
*
* @param \Illuminate\Database\Eloquent\Builder $from
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function mergeConstraintsFrom(Builder $from)
{
$whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
// Here we have some other query that we want to merge the where constraints from. We will
// copy over any where constraints on the query as well as remove any global scopes the
// query might have removed. Then we will return ourselves with the finished merging.
return $this->withoutGlobalScopes(
$from->removedScopes()
)->mergeWheres(
$from->getQuery()->wheres, $whereBindings
);
}
/**
* Add a sub-query count clause to this query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $operator
* @param int $count
* @param string $boolean
* @return $this
*/
protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
{
$this->query->addBinding($query->getBindings(), 'where');
return $this->where(
new Expression('('.$query->toSql().')'),
$operator,
is_numeric($count) ? new Expression($count) : $count,
$boolean
);
}
/**
* Get the "has relation" base query instance.
*
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
protected function getRelationWithoutConstraints($relation)
{
return Relation::noConstraints(function () use ($relation) {
return $this->getModel()->{$relation}();
});
}
/**
* Check if we can run an "exists" query to optimize performance.
*
* @param string $operator
* @param int $count
* @return bool
*/
protected function canUseExistsForExistenceCheck($operator, $count)
{
return ($operator === '>=' || $operator === '<') && $count === 1;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,32 @@
<?php
namespace Illuminate\Database\Events;
abstract class ConnectionEvent
{
/**
* The name of the connection.
*
* @var string
*/
public $connectionName;
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
public $connection;
/**
* Create a new event instance.
*
* @param \Illuminate\Database\Connection $connection
* @return void
*/
public function __construct($connection)
{
$this->connection = $connection;
$this->connectionName = $connection->getName();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Database\Events;
class MigrationEnded extends MigrationEvent
{
//
}

View File

@ -0,0 +1,36 @@
<?php
namespace Illuminate\Database\Events;
use Illuminate\Contracts\Database\Events\MigrationEvent as MigrationEventContract;
use Illuminate\Database\Migrations\Migration;
abstract class MigrationEvent implements MigrationEventContract
{
/**
* An migration instance.
*
* @var \Illuminate\Database\Migrations\Migration
*/
public $migration;
/**
* The migration method that was called.
*
* @var string
*/
public $method;
/**
* Create a new event instance.
*
* @param \Illuminate\Database\Migrations\Migration $migration
* @param string $method
* @return void
*/
public function __construct(Migration $migration, $method)
{
$this->method = $method;
$this->migration = $migration;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Database\Events;
class MigrationStarted extends MigrationEvent
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace Illuminate\Database\Events;
use Illuminate\Contracts\Database\Events\MigrationEvent as MigrationEventContract;
class MigrationsEnded implements MigrationEventContract
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace Illuminate\Database\Events;
use Illuminate\Contracts\Database\Events\MigrationEvent as MigrationEventContract;
class MigrationsStarted implements MigrationEventContract
{
//
}

View File

@ -0,0 +1,24 @@
<?php
namespace Illuminate\Database\Events;
class NoPendingMigrations
{
/**
* The migration method that was called.
*
* @var string
*/
public $method;
/**
* Create a new event instance.
*
* @param string $method
* @return void
*/
public function __construct($method)
{
$this->method = $method;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Illuminate\Database\Events;
class QueryExecuted
{
/**
* The SQL query that was executed.
*
* @var string
*/
public $sql;
/**
* The array of query bindings.
*
* @var array
*/
public $bindings;
/**
* The number of milliseconds it took to execute the query.
*
* @var float
*/
public $time;
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
public $connection;
/**
* The database connection name.
*
* @var string
*/
public $connectionName;
/**
* Create a new event instance.
*
* @param string $sql
* @param array $bindings
* @param float|null $time
* @param \Illuminate\Database\Connection $connection
* @return void
*/
public function __construct($sql, $bindings, $time, $connection)
{
$this->sql = $sql;
$this->time = $time;
$this->bindings = $bindings;
$this->connection = $connection;
$this->connectionName = $connection->getName();
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Illuminate\Database\Events;
class StatementPrepared
{
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
public $connection;
/**
* The PDO statement.
*
* @var \PDOStatement
*/
public $statement;
/**
* Create a new event instance.
*
* @param \Illuminate\Database\Connection $connection
* @param \PDOStatement $statement
* @return void
*/
public function __construct($connection, $statement)
{
$this->statement = $statement;
$this->connection = $connection;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Database\Events;
class TransactionBeginning extends ConnectionEvent
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Database\Events;
class TransactionCommitted extends ConnectionEvent
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Illuminate\Database\Events;
class TransactionRolledBack extends ConnectionEvent
{
//
}

221
vendor/illuminate/database/Grammar.php vendored Executable file
View File

@ -0,0 +1,221 @@
<?php
namespace Illuminate\Database;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Traits\Macroable;
abstract class Grammar
{
use Macroable;
/**
* The grammar table prefix.
*
* @var string
*/
protected $tablePrefix = '';
/**
* Wrap an array of values.
*
* @param array $values
* @return array
*/
public function wrapArray(array $values)
{
return array_map([$this, 'wrap'], $values);
}
/**
* Wrap a table in keyword identifiers.
*
* @param \Illuminate\Database\Query\Expression|string $table
* @return string
*/
public function wrapTable($table)
{
if (! $this->isExpression($table)) {
return $this->wrap($this->tablePrefix.$table, true);
}
return $this->getValue($table);
}
/**
* Wrap a value in keyword identifiers.
*
* @param \Illuminate\Database\Query\Expression|string $value
* @param bool $prefixAlias
* @return string
*/
public function wrap($value, $prefixAlias = false)
{
if ($this->isExpression($value)) {
return $this->getValue($value);
}
// If the value being wrapped has a column alias we will need to separate out
// the pieces so we can wrap each of the segments of the expression on its
// own, and then join these both back together using the "as" connector.
if (stripos($value, ' as ') !== false) {
return $this->wrapAliasedValue($value, $prefixAlias);
}
return $this->wrapSegments(explode('.', $value));
}
/**
* Wrap a value that has an alias.
*
* @param string $value
* @param bool $prefixAlias
* @return string
*/
protected function wrapAliasedValue($value, $prefixAlias = false)
{
$segments = preg_split('/\s+as\s+/i', $value);
// If we are wrapping a table we need to prefix the alias with the table prefix
// as well in order to generate proper syntax. If this is a column of course
// no prefix is necessary. The condition will be true when from wrapTable.
if ($prefixAlias) {
$segments[1] = $this->tablePrefix.$segments[1];
}
return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[1]);
}
/**
* Wrap the given value segments.
*
* @param array $segments
* @return string
*/
protected function wrapSegments($segments)
{
return collect($segments)->map(function ($segment, $key) use ($segments) {
return $key == 0 && count($segments) > 1
? $this->wrapTable($segment)
: $this->wrapValue($segment);
})->implode('.');
}
/**
* Wrap a single string in keyword identifiers.
*
* @param string $value
* @return string
*/
protected function wrapValue($value)
{
if ($value !== '*') {
return '"'.str_replace('"', '""', $value).'"';
}
return $value;
}
/**
* Convert an array of column names into a delimited string.
*
* @param array $columns
* @return string
*/
public function columnize(array $columns)
{
return implode(', ', array_map([$this, 'wrap'], $columns));
}
/**
* Create query parameter place-holders for an array.
*
* @param array $values
* @return string
*/
public function parameterize(array $values)
{
return implode(', ', array_map([$this, 'parameter'], $values));
}
/**
* Get the appropriate query parameter place-holder for a value.
*
* @param mixed $value
* @return string
*/
public function parameter($value)
{
return $this->isExpression($value) ? $this->getValue($value) : '?';
}
/**
* Quote the given string literal.
*
* @param string|array $value
* @return string
*/
public function quoteString($value)
{
if (is_array($value)) {
return implode(', ', array_map([$this, __FUNCTION__], $value));
}
return "'$value'";
}
/**
* Determine if the given value is a raw expression.
*
* @param mixed $value
* @return bool
*/
public function isExpression($value)
{
return $value instanceof Expression;
}
/**
* Get the value of a raw expression.
*
* @param \Illuminate\Database\Query\Expression $expression
* @return string
*/
public function getValue($expression)
{
return $expression->getValue();
}
/**
* Get the format for database stored dates.
*
* @return string
*/
public function getDateFormat()
{
return 'Y-m-d H:i:s';
}
/**
* Get the grammar's table prefix.
*
* @return string
*/
public function getTablePrefix()
{
return $this->tablePrefix;
}
/**
* Set the grammar's table prefix.
*
* @param string $prefix
* @return $this
*/
public function setTablePrefix($prefix)
{
$this->tablePrefix = $prefix;
return $this;
}
}

21
vendor/illuminate/database/LICENSE.md vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,225 @@
<?php
namespace Illuminate\Database;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Database\Console\Migrations\InstallCommand;
use Illuminate\Database\Console\Migrations\MigrateCommand;
use Illuminate\Database\Console\Migrations\MigrateMakeCommand;
use Illuminate\Database\Console\Migrations\RefreshCommand;
use Illuminate\Database\Console\Migrations\ResetCommand;
use Illuminate\Database\Console\Migrations\RollbackCommand;
use Illuminate\Database\Console\Migrations\StatusCommand;
use Illuminate\Database\Migrations\DatabaseMigrationRepository;
use Illuminate\Database\Migrations\MigrationCreator;
use Illuminate\Database\Migrations\Migrator;
use Illuminate\Support\ServiceProvider;
class MigrationServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* The commands to be registered.
*
* @var array
*/
protected $commands = [
'Migrate' => 'command.migrate',
'MigrateFresh' => 'command.migrate.fresh',
'MigrateInstall' => 'command.migrate.install',
'MigrateRefresh' => 'command.migrate.refresh',
'MigrateReset' => 'command.migrate.reset',
'MigrateRollback' => 'command.migrate.rollback',
'MigrateStatus' => 'command.migrate.status',
'MigrateMake' => 'command.migrate.make',
];
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerRepository();
$this->registerMigrator();
$this->registerCreator();
$this->registerCommands($this->commands);
}
/**
* Register the migration repository service.
*
* @return void
*/
protected function registerRepository()
{
$this->app->singleton('migration.repository', function ($app) {
$table = $app['config']['database.migrations'];
return new DatabaseMigrationRepository($app['db'], $table);
});
}
/**
* Register the migrator service.
*
* @return void
*/
protected function registerMigrator()
{
// The migrator is responsible for actually running and rollback the migration
// files in the application. We'll pass in our database connection resolver
// so the migrator can resolve any of these connections when it needs to.
$this->app->singleton('migrator', function ($app) {
$repository = $app['migration.repository'];
return new Migrator($repository, $app['db'], $app['files'], $app['events']);
});
}
/**
* Register the migration creator.
*
* @return void
*/
protected function registerCreator()
{
$this->app->singleton('migration.creator', function ($app) {
return new MigrationCreator($app['files'], $app->basePath('stubs'));
});
}
/**
* Register the given commands.
*
* @param array $commands
* @return void
*/
protected function registerCommands(array $commands)
{
foreach (array_keys($commands) as $command) {
$this->{"register{$command}Command"}();
}
$this->commands(array_values($commands));
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateCommand()
{
$this->app->singleton('command.migrate', function ($app) {
return new MigrateCommand($app['migrator']);
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateFreshCommand()
{
$this->app->singleton('command.migrate.fresh', function () {
return new FreshCommand;
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateInstallCommand()
{
$this->app->singleton('command.migrate.install', function ($app) {
return new InstallCommand($app['migration.repository']);
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateMakeCommand()
{
$this->app->singleton('command.migrate.make', function ($app) {
// Once we have the migration creator registered, we will create the command
// and inject the creator. The creator is responsible for the actual file
// creation of the migrations, and may be extended by these developers.
$creator = $app['migration.creator'];
$composer = $app['composer'];
return new MigrateMakeCommand($creator, $composer);
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateRefreshCommand()
{
$this->app->singleton('command.migrate.refresh', function () {
return new RefreshCommand;
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateResetCommand()
{
$this->app->singleton('command.migrate.reset', function ($app) {
return new ResetCommand($app['migrator']);
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateRollbackCommand()
{
$this->app->singleton('command.migrate.rollback', function ($app) {
return new RollbackCommand($app['migrator']);
});
}
/**
* Register the command.
*
* @return void
*/
protected function registerMigrateStatusCommand()
{
$this->app->singleton('command.migrate.status', function ($app) {
return new StatusCommand($app['migrator']);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array_merge([
'migrator', 'migration.repository', 'migration.creator',
], array_values($this->commands));
}
}

View File

@ -0,0 +1,212 @@
<?php
namespace Illuminate\Database\Migrations;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
class DatabaseMigrationRepository implements MigrationRepositoryInterface
{
/**
* The database connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* The name of the migration table.
*
* @var string
*/
protected $table;
/**
* The name of the database connection to use.
*
* @var string
*/
protected $connection;
/**
* Create a new database migration repository instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @param string $table
* @return void
*/
public function __construct(Resolver $resolver, $table)
{
$this->table = $table;
$this->resolver = $resolver;
}
/**
* Get the completed migrations.
*
* @return array
*/
public function getRan()
{
return $this->table()
->orderBy('batch', 'asc')
->orderBy('migration', 'asc')
->pluck('migration')->all();
}
/**
* Get list of migrations.
*
* @param int $steps
* @return array
*/
public function getMigrations($steps)
{
$query = $this->table()->where('batch', '>=', '1');
return $query->orderBy('batch', 'desc')
->orderBy('migration', 'desc')
->take($steps)->get()->all();
}
/**
* Get the last migration batch.
*
* @return array
*/
public function getLast()
{
$query = $this->table()->where('batch', $this->getLastBatchNumber());
return $query->orderBy('migration', 'desc')->get()->all();
}
/**
* Get the completed migrations with their batch numbers.
*
* @return array
*/
public function getMigrationBatches()
{
return $this->table()
->orderBy('batch', 'asc')
->orderBy('migration', 'asc')
->pluck('batch', 'migration')->all();
}
/**
* Log that a migration was run.
*
* @param string $file
* @param int $batch
* @return void
*/
public function log($file, $batch)
{
$record = ['migration' => $file, 'batch' => $batch];
$this->table()->insert($record);
}
/**
* Remove a migration from the log.
*
* @param object $migration
* @return void
*/
public function delete($migration)
{
$this->table()->where('migration', $migration->migration)->delete();
}
/**
* Get the next migration batch number.
*
* @return int
*/
public function getNextBatchNumber()
{
return $this->getLastBatchNumber() + 1;
}
/**
* Get the last migration batch number.
*
* @return int
*/
public function getLastBatchNumber()
{
return $this->table()->max('batch');
}
/**
* Create the migration repository data store.
*
* @return void
*/
public function createRepository()
{
$schema = $this->getConnection()->getSchemaBuilder();
$schema->create($this->table, function ($table) {
// The migrations table is responsible for keeping track of which of the
// migrations have actually run for the application. We'll create the
// table to hold the migration file's path as well as the batch ID.
$table->increments('id');
$table->string('migration');
$table->integer('batch');
});
}
/**
* Determine if the migration repository exists.
*
* @return bool
*/
public function repositoryExists()
{
$schema = $this->getConnection()->getSchemaBuilder();
return $schema->hasTable($this->table);
}
/**
* Get a query builder for the migration table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function table()
{
return $this->getConnection()->table($this->table)->useWritePdo();
}
/**
* Get the connection resolver instance.
*
* @return \Illuminate\Database\ConnectionResolverInterface
*/
public function getConnectionResolver()
{
return $this->resolver;
}
/**
* Resolve the database connection instance.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->resolver->connection($this->connection);
}
/**
* Set the information source to gather data.
*
* @param string $name
* @return void
*/
public function setSource($name)
{
$this->connection = $name;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Illuminate\Database\Migrations;
abstract class Migration
{
/**
* The name of the database connection to use.
*
* @var string|null
*/
protected $connection;
/**
* Enables, if supported, wrapping the migration within a transaction.
*
* @var bool
*/
public $withinTransaction = true;
/**
* Get the migration connection name.
*
* @return string|null
*/
public function getConnection()
{
return $this->connection;
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace Illuminate\Database\Migrations;
use Closure;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;
use InvalidArgumentException;
class MigrationCreator
{
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The custom app stubs directory.
*
* @var string
*/
protected $customStubPath;
/**
* The registered post create hooks.
*
* @var array
*/
protected $postCreate = [];
/**
* Create a new migration creator instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $customStubPath
* @return void
*/
public function __construct(Filesystem $files, $customStubPath)
{
$this->files = $files;
$this->customStubPath = $customStubPath;
}
/**
* Create a new migration at the given path.
*
* @param string $name
* @param string $path
* @param string|null $table
* @param bool $create
* @return string
*
* @throws \Exception
*/
public function create($name, $path, $table = null, $create = false)
{
$this->ensureMigrationDoesntAlreadyExist($name, $path);
// First we will get the stub file for the migration, which serves as a type
// of template for the migration. Once we have those we will populate the
// various place-holders, save the file, and run the post create event.
$stub = $this->getStub($table, $create);
$this->files->put(
$path = $this->getPath($name, $path),
$this->populateStub($name, $stub, $table)
);
// Next, we will fire any hooks that are supposed to fire after a migration is
// created. Once that is done we'll be ready to return the full path to the
// migration file so it can be used however it's needed by the developer.
$this->firePostCreateHooks($table);
return $path;
}
/**
* Ensure that a migration with the given name doesn't already exist.
*
* @param string $name
* @param string $migrationPath
* @return void
*
* @throws \InvalidArgumentException
*/
protected function ensureMigrationDoesntAlreadyExist($name, $migrationPath = null)
{
if (! empty($migrationPath)) {
$migrationFiles = $this->files->glob($migrationPath.'/*.php');
foreach ($migrationFiles as $migrationFile) {
$this->files->requireOnce($migrationFile);
}
}
if (class_exists($className = $this->getClassName($name))) {
throw new InvalidArgumentException("A {$className} class already exists.");
}
}
/**
* Get the migration stub file.
*
* @param string|null $table
* @param bool $create
* @return string
*/
protected function getStub($table, $create)
{
if (is_null($table)) {
$stub = $this->files->exists($customPath = $this->customStubPath.'/migration.stub')
? $customPath
: $this->stubPath().'/migration.stub';
} elseif ($create) {
$stub = $this->files->exists($customPath = $this->customStubPath.'/migration.create.stub')
? $customPath
: $this->stubPath().'/migration.create.stub';
} else {
$stub = $this->files->exists($customPath = $this->customStubPath.'/migration.update.stub')
? $customPath
: $this->stubPath().'/migration.update.stub';
}
return $this->files->get($stub);
}
/**
* Populate the place-holders in the migration stub.
*
* @param string $name
* @param string $stub
* @param string|null $table
* @return string
*/
protected function populateStub($name, $stub, $table)
{
$stub = str_replace(
['DummyClass', '{{ class }}', '{{class}}'],
$this->getClassName($name), $stub
);
// Here we will replace the table place-holders with the table specified by
// the developer, which is useful for quickly creating a tables creation
// or update migration from the console instead of typing it manually.
if (! is_null($table)) {
$stub = str_replace(
['DummyTable', '{{ table }}', '{{table}}'],
$table, $stub
);
}
return $stub;
}
/**
* Get the class name of a migration name.
*
* @param string $name
* @return string
*/
protected function getClassName($name)
{
return Str::studly($name);
}
/**
* Get the full path to the migration.
*
* @param string $name
* @param string $path
* @return string
*/
protected function getPath($name, $path)
{
return $path.'/'.$this->getDatePrefix().'_'.$name.'.php';
}
/**
* Fire the registered post create hooks.
*
* @param string|null $table
* @return void
*/
protected function firePostCreateHooks($table)
{
foreach ($this->postCreate as $callback) {
$callback($table);
}
}
/**
* Register a post migration create hook.
*
* @param \Closure $callback
* @return void
*/
public function afterCreate(Closure $callback)
{
$this->postCreate[] = $callback;
}
/**
* Get the date prefix for the migration.
*
* @return string
*/
protected function getDatePrefix()
{
return date('Y_m_d_His');
}
/**
* Get the path to the stubs.
*
* @return string
*/
public function stubPath()
{
return __DIR__.'/stubs';
}
/**
* Get the filesystem instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace Illuminate\Database\Migrations;
interface MigrationRepositoryInterface
{
/**
* Get the completed migrations.
*
* @return array
*/
public function getRan();
/**
* Get list of migrations.
*
* @param int $steps
* @return array
*/
public function getMigrations($steps);
/**
* Get the last migration batch.
*
* @return array
*/
public function getLast();
/**
* Get the completed migrations with their batch numbers.
*
* @return array
*/
public function getMigrationBatches();
/**
* Log that a migration was run.
*
* @param string $file
* @param int $batch
* @return void
*/
public function log($file, $batch);
/**
* Remove a migration from the log.
*
* @param object $migration
* @return void
*/
public function delete($migration);
/**
* Get the next migration batch number.
*
* @return int
*/
public function getNextBatchNumber();
/**
* Create the migration repository data store.
*
* @return void
*/
public function createRepository();
/**
* Determine if the migration repository exists.
*
* @return bool
*/
public function repositoryExists();
/**
* Set the information source to gather data.
*
* @param string $name
* @return void
*/
public function setSource($name);
}

View File

@ -0,0 +1,659 @@
<?php
namespace Illuminate\Database\Migrations;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Events\MigrationEnded;
use Illuminate\Database\Events\MigrationsEnded;
use Illuminate\Database\Events\MigrationsStarted;
use Illuminate\Database\Events\MigrationStarted;
use Illuminate\Database\Events\NoPendingMigrations;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\Console\Output\OutputInterface;
class Migrator
{
/**
* The event dispatcher instance.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* The migration repository implementation.
*
* @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
*/
protected $repository;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* The name of the default connection.
*
* @var string
*/
protected $connection;
/**
* The paths to all of the migration files.
*
* @var array
*/
protected $paths = [];
/**
* The output interface implementation.
*
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* Create a new migrator instance.
*
* @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @param \Illuminate\Filesystem\Filesystem $files
* @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher
* @return void
*/
public function __construct(MigrationRepositoryInterface $repository,
Resolver $resolver,
Filesystem $files,
Dispatcher $dispatcher = null)
{
$this->files = $files;
$this->events = $dispatcher;
$this->resolver = $resolver;
$this->repository = $repository;
}
/**
* Run the pending migrations at a given path.
*
* @param array|string $paths
* @param array $options
* @return array
*/
public function run($paths = [], array $options = [])
{
// Once we grab all of the migration files for the path, we will compare them
// against the migrations that have already been run for this package then
// run each of the outstanding migrations against a database connection.
$files = $this->getMigrationFiles($paths);
$this->requireFiles($migrations = $this->pendingMigrations(
$files, $this->repository->getRan()
));
// Once we have all these migrations that are outstanding we are ready to run
// we will go ahead and run them "up". This will execute each migration as
// an operation against a database. Then we'll return this list of them.
$this->runPending($migrations, $options);
return $migrations;
}
/**
* Get the migration files that have not yet run.
*
* @param array $files
* @param array $ran
* @return array
*/
protected function pendingMigrations($files, $ran)
{
return Collection::make($files)
->reject(function ($file) use ($ran) {
return in_array($this->getMigrationName($file), $ran);
})->values()->all();
}
/**
* Run an array of migrations.
*
* @param array $migrations
* @param array $options
* @return void
*/
public function runPending(array $migrations, array $options = [])
{
// First we will just make sure that there are any migrations to run. If there
// aren't, we will just make a note of it to the developer so they're aware
// that all of the migrations have been run against this database system.
if (count($migrations) === 0) {
$this->fireMigrationEvent(new NoPendingMigrations('up'));
$this->note('<info>Nothing to migrate.</info>');
return;
}
// Next, we will get the next batch number for the migrations so we can insert
// correct batch number in the database migrations repository when we store
// each migration's execution. We will also extract a few of the options.
$batch = $this->repository->getNextBatchNumber();
$pretend = $options['pretend'] ?? false;
$step = $options['step'] ?? false;
$this->fireMigrationEvent(new MigrationsStarted);
// 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
// that the migration was run so we don't repeat it next time we execute.
foreach ($migrations as $file) {
$this->runUp($file, $batch, $pretend);
if ($step) {
$batch++;
}
}
$this->fireMigrationEvent(new MigrationsEnded);
}
/**
* Run "up" a migration instance.
*
* @param string $file
* @param int $batch
* @param bool $pretend
* @return void
*/
protected function runUp($file, $batch, $pretend)
{
// 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)
);
if ($pretend) {
return $this->pretendToRun($migration, 'up');
}
$this->note("<comment>Migrating:</comment> {$name}");
$startTime = microtime(true);
$this->runMigration($migration, 'up');
$runTime = round(microtime(true) - $startTime, 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)");
}
/**
* Rollback the last migration operation.
*
* @param array|string $paths
* @param array $options
* @return array
*/
public function rollback($paths = [], array $options = [])
{
// We want to pull in the last batch of migrations that ran on the previous
// migration operation. We'll then reverse those migrations and run each
// of them "down" to reverse the last migration "operation" which ran.
$migrations = $this->getMigrationsForRollback($options);
if (count($migrations) === 0) {
$this->fireMigrationEvent(new NoPendingMigrations('down'));
$this->note('<info>Nothing to rollback.</info>');
return [];
}
return $this->rollbackMigrations($migrations, $paths, $options);
}
/**
* Get the migrations for a rollback operation.
*
* @param array $options
* @return array
*/
protected function getMigrationsForRollback(array $options)
{
if (($steps = $options['step'] ?? 0) > 0) {
return $this->repository->getMigrations($steps);
}
return $this->repository->getLast();
}
/**
* Rollback the given migrations.
*
* @param array $migrations
* @param array|string $paths
* @param array $options
* @return array
*/
protected function rollbackMigrations(array $migrations, $paths, array $options)
{
$rolledBack = [];
$this->requireFiles($files = $this->getMigrationFiles($paths));
$this->fireMigrationEvent(new MigrationsStarted);
// 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
// repository already returns these migration's names in reverse order.
foreach ($migrations as $migration) {
$migration = (object) $migration;
if (! $file = Arr::get($files, $migration->migration)) {
$this->note("<fg=red>Migration not found:</> {$migration->migration}");
continue;
}
$rolledBack[] = $file;
$this->runDown(
$file, $migration,
$options['pretend'] ?? false
);
}
$this->fireMigrationEvent(new MigrationsEnded);
return $rolledBack;
}
/**
* Rolls all of the currently applied migrations back.
*
* @param array|string $paths
* @param bool $pretend
* @return array
*/
public function reset($paths = [], $pretend = false)
{
// Next, we will reverse the migration list so we can run them back in the
// correct order for resetting this database. This will allow us to get
// the database back into its "empty" state ready for the migrations.
$migrations = array_reverse($this->repository->getRan());
if (count($migrations) === 0) {
$this->note('<info>Nothing to rollback.</info>');
return [];
}
return $this->resetMigrations($migrations, $paths, $pretend);
}
/**
* Reset the given migrations.
*
* @param array $migrations
* @param array $paths
* @param bool $pretend
* @return array
*/
protected function resetMigrations(array $migrations, array $paths, $pretend = false)
{
// Since the getRan method that retrieves the migration name just gives us the
// migration name, we will format the names into objects with the name as a
// property on the objects so that we can pass it to the rollback method.
$migrations = collect($migrations)->map(function ($m) {
return (object) ['migration' => $m];
})->all();
return $this->rollbackMigrations(
$migrations, $paths, compact('pretend')
);
}
/**
* Run "down" a migration instance.
*
* @param string $file
* @param object $migration
* @param bool $pretend
* @return void
*/
protected function runDown($file, $migration, $pretend)
{
// 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)
);
$this->note("<comment>Rolling back:</comment> {$name}");
if ($pretend) {
return $this->pretendToRun($instance, 'down');
}
$startTime = microtime(true);
$this->runMigration($instance, 'down');
$runTime = round(microtime(true) - $startTime, 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)");
}
/**
* Run a migration inside a transaction if the database supports it.
*
* @param object $migration
* @param string $method
* @return void
*/
protected function runMigration($migration, $method)
{
$connection = $this->resolveConnection(
$migration->getConnection()
);
$callback = function () use ($migration, $method) {
if (method_exists($migration, $method)) {
$this->fireMigrationEvent(new MigrationStarted($migration, $method));
$migration->{$method}();
$this->fireMigrationEvent(new MigrationEnded($migration, $method));
}
};
$this->getSchemaGrammar($connection)->supportsSchemaTransactions()
&& $migration->withinTransaction
? $connection->transaction($callback)
: $callback();
}
/**
* Pretend to run the migrations.
*
* @param object $migration
* @param string $method
* @return void
*/
protected function pretendToRun($migration, $method)
{
foreach ($this->getQueries($migration, $method) as $query) {
$name = get_class($migration);
$this->note("<info>{$name}:</info> {$query['query']}");
}
}
/**
* Get all of the queries that would be run for a migration.
*
* @param object $migration
* @param string $method
* @return array
*/
protected function getQueries($migration, $method)
{
// Now that we have the connections we can resolve it and pretend to run the
// queries against the database returning the array of raw SQL statements
// that would get fired against the database system for this migration.
$db = $this->resolveConnection(
$migration->getConnection()
);
return $db->pretend(function () use ($migration, $method) {
if (method_exists($migration, $method)) {
$migration->{$method}();
}
});
}
/**
* Resolve a migration instance from a file.
*
* @param string $file
* @return object
*/
public function resolve($file)
{
$class = Str::studly(implode('_', array_slice(explode('_', $file), 4)));
return new $class;
}
/**
* Get all of the migration files in a given path.
*
* @param string|array $paths
* @return array
*/
public function getMigrationFiles($paths)
{
return Collection::make($paths)->flatMap(function ($path) {
return Str::endsWith($path, '.php') ? [$path] : $this->files->glob($path.'/*_*.php');
})->filter()->values()->keyBy(function ($file) {
return $this->getMigrationName($file);
})->sortBy(function ($file, $key) {
return $key;
})->all();
}
/**
* Require in all the migration files in a given path.
*
* @param array $files
* @return void
*/
public function requireFiles(array $files)
{
foreach ($files as $file) {
$this->files->requireOnce($file);
}
}
/**
* Get the name of the migration.
*
* @param string $path
* @return string
*/
public function getMigrationName($path)
{
return str_replace('.php', '', basename($path));
}
/**
* Register a custom migration path.
*
* @param string $path
* @return void
*/
public function path($path)
{
$this->paths = array_unique(array_merge($this->paths, [$path]));
}
/**
* Get all of the custom migration paths.
*
* @return array
*/
public function paths()
{
return $this->paths;
}
/**
* Get the default connection name.
*
* @return string
*/
public function getConnection()
{
return $this->connection;
}
/**
* Execute the given callback using the given connection as the default connection.
*
* @param string $name
* @param callable $callback
* @return mixed
*/
public function usingConnection($name, callable $callback)
{
$previousConnection = $this->resolver->getDefaultConnection();
$this->setConnection($name);
return tap($callback(), function () use ($previousConnection) {
$this->setConnection($previousConnection);
});
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setConnection($name)
{
if (! is_null($name)) {
$this->resolver->setDefaultConnection($name);
}
$this->repository->setSource($name);
$this->connection = $name;
}
/**
* Resolve the database connection instance.
*
* @param string $connection
* @return \Illuminate\Database\Connection
*/
public function resolveConnection($connection)
{
return $this->resolver->connection($connection ?: $this->connection);
}
/**
* Get the schema grammar out of a migration connection.
*
* @param \Illuminate\Database\Connection $connection
* @return \Illuminate\Database\Schema\Grammars\Grammar
*/
protected function getSchemaGrammar($connection)
{
if (is_null($grammar = $connection->getSchemaGrammar())) {
$connection->useDefaultSchemaGrammar();
$grammar = $connection->getSchemaGrammar();
}
return $grammar;
}
/**
* Get the migration repository instance.
*
* @return \Illuminate\Database\Migrations\MigrationRepositoryInterface
*/
public function getRepository()
{
return $this->repository;
}
/**
* Determine if the migration repository exists.
*
* @return bool
*/
public function repositoryExists()
{
return $this->repository->repositoryExists();
}
/**
* Get the file system instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
/**
* Set the output implementation that should be used by the console.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return $this
*/
public function setOutput(OutputInterface $output)
{
$this->output = $output;
return $this;
}
/**
* Write a note to the console's output.
*
* @param string $message
* @return void
*/
protected function note($message)
{
if ($this->output) {
$this->output->writeln($message);
}
}
/**
* Fire the given event for the migration.
*
* @param \Illuminate\Contracts\Database\Events\MigrationEvent $event
* @return void
*/
public function fireMigrationEvent($event)
{
if ($this->events) {
$this->events->dispatch($event);
}
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class {{ class }} extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('{{ table }}', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('{{ table }}');
}
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class {{ class }} extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class {{ class }} extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('{{ table }}', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('{{ table }}', function (Blueprint $table) {
//
});
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Illuminate\Database;
use Doctrine\DBAL\Driver\PDOMySql\Driver as DoctrineDriver;
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;
class MySqlConnection extends Connection
{
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\MySqlGrammar
*/
protected function getDefaultQueryGrammar()
{
return $this->withTablePrefix(new QueryGrammar);
}
/**
* Get a schema builder instance for the connection.
*
* @return \Illuminate\Database\Schema\MySqlBuilder
*/
public function getSchemaBuilder()
{
if (is_null($this->schemaGrammar)) {
$this->useDefaultSchemaGrammar();
}
return new MySqlBuilder($this);
}
/**
* Get the default schema grammar instance.
*
* @return \Illuminate\Database\Schema\Grammars\MySqlGrammar
*/
protected function getDefaultSchemaGrammar()
{
return $this->withTablePrefix(new SchemaGrammar);
}
/**
* Get the default post processor instance.
*
* @return \Illuminate\Database\Query\Processors\MySqlProcessor
*/
protected function getDefaultPostProcessor()
{
return new MySqlProcessor;
}
/**
* Get the Doctrine DBAL driver.
*
* @return \Doctrine\DBAL\Driver\PDOMySql\Driver
*/
protected function getDoctrineDriver()
{
return new DoctrineDriver;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Illuminate\Database;
use Doctrine\DBAL\Driver\PDOPgSql\Driver as DoctrineDriver;
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 PDO;
class PostgresConnection extends Connection
{
/**
* Bind values to their parameters in the given statement.
*
* @param \PDOStatement $statement
* @param array $bindings
* @return void
*/
public function bindValues($statement, $bindings)
{
foreach ($bindings as $key => $value) {
if (is_int($value)) {
$pdoParam = PDO::PARAM_INT;
} elseif (is_resource($value)) {
$pdoParam = PDO::PARAM_LOB;
} else {
$pdoParam = PDO::PARAM_STR;
}
$statement->bindValue(
is_string($key) ? $key : $key + 1,
$value,
$pdoParam
);
}
}
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\PostgresGrammar
*/
protected function getDefaultQueryGrammar()
{
return $this->withTablePrefix(new QueryGrammar);
}
/**
* Get a schema builder instance for the connection.
*
* @return \Illuminate\Database\Schema\PostgresBuilder
*/
public function getSchemaBuilder()
{
if (is_null($this->schemaGrammar)) {
$this->useDefaultSchemaGrammar();
}
return new PostgresBuilder($this);
}
/**
* Get the default schema grammar instance.
*
* @return \Illuminate\Database\Schema\Grammars\PostgresGrammar
*/
protected function getDefaultSchemaGrammar()
{
return $this->withTablePrefix(new SchemaGrammar);
}
/**
* Get the default post processor instance.
*
* @return \Illuminate\Database\Query\Processors\PostgresProcessor
*/
protected function getDefaultPostProcessor()
{
return new PostgresProcessor;
}
/**
* Get the Doctrine DBAL driver.
*
* @return \Doctrine\DBAL\Driver\PDOPgSql\Driver
*/
protected function getDoctrineDriver()
{
return new DoctrineDriver;
}
}

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