Capsule/Manager.php000075500000012400147536122140010230 0ustar00setupContainer($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); } } ClassMorphViolationException.php000064400000001015147536122140013076 0ustar00model = $class; } } Concerns/BuildsQueries.php000064400000043161147536122140011621 0ustar00enforceOrderBy(); $page = 1; do { // We'll execute the query for the given page and get the results. If there are // no results we can just break and return from here. When there are results // we will call the callback with the current chunk of these results here. $results = $this->forPage($page, $count)->get(); $countResults = $results->count(); if ($countResults == 0) { break; } // On each chunk result set, we will pass them to the callback and then let the // developer take care of everything within the callback, which allows us to // keep the memory low for spinning through large result sets for working. if ($callback($results, $page) === false) { return false; } unset($results); $page++; } while ($countResults == $count); return true; } /** * Run a map over each item while chunking. * * @param callable $callback * @param int $count * @return \Illuminate\Support\Collection */ public function chunkMap(callable $callback, $count = 1000) { $collection = Collection::make(); $this->chunk($count, function ($items) use ($collection, $callback) { $items->each(function ($item) use ($collection, $callback) { $collection->push($callback($item)); }); }); return $collection; } /** * Execute a callback over each item while chunking. * * @param callable $callback * @param int $count * @return bool * * @throws \RuntimeException */ public function each(callable $callback, $count = 1000) { return $this->chunk($count, function ($results) use ($callback) { foreach ($results as $key => $value) { if ($callback($value, $key) === false) { return false; } } }); } /** * Chunk the results of a query by comparing IDs. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @return bool */ public function chunkById($count, callable $callback, $column = null, $alias = null) { return $this->orderedChunkById($count, $callback, $column, $alias); } /** * Chunk the results of a query by comparing IDs in descending order. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @return bool */ public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null) { return $this->orderedChunkById($count, $callback, $column, $alias, descending: true); } /** * Chunk the results of a query by comparing IDs in a given order. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @param bool $descending * @return bool */ public function orderedChunkById($count, callable $callback, $column = null, $alias = null, $descending = false) { $column ??= $this->defaultKeyName(); $alias ??= $column; $lastId = null; $page = 1; do { $clone = clone $this; // We'll execute the query for the given page and get the results. If there are // no results we can just break and return from here. When there are results // we will call the callback with the current chunk of these results here. if ($descending) { $results = $clone->forPageBeforeId($count, $lastId, $column)->get(); } else { $results = $clone->forPageAfterId($count, $lastId, $column)->get(); } $countResults = $results->count(); if ($countResults == 0) { break; } // On each chunk result set, we will pass them to the callback and then let the // developer take care of everything within the callback, which allows us to // keep the memory low for spinning through large result sets for working. if ($callback($results, $page) === false) { return false; } $lastId = data_get($results->last(), $alias); if ($lastId === null) { throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result."); } unset($results); $page++; } while ($countResults == $count); return true; } /** * Execute a callback over each item while chunking by ID. * * @param callable $callback * @param int $count * @param string|null $column * @param string|null $alias * @return bool */ public function eachById(callable $callback, $count = 1000, $column = null, $alias = null) { return $this->chunkById($count, function ($results, $page) use ($callback, $count) { foreach ($results as $key => $value) { if ($callback($value, (($page - 1) * $count) + $key) === false) { return false; } } }, $column, $alias); } /** * Query lazily, by chunks of the given size. * * @param int $chunkSize * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazy($chunkSize = 1000) { if ($chunkSize < 1) { throw new InvalidArgumentException('The chunk size should be at least 1'); } $this->enforceOrderBy(); return LazyCollection::make(function () use ($chunkSize) { $page = 1; while (true) { $results = $this->forPage($page++, $chunkSize)->get(); foreach ($results as $result) { yield $result; } if ($results->count() < $chunkSize) { return; } } }); } /** * Query lazily, by chunking the results of a query by comparing IDs. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { return $this->orderedLazyById($chunkSize, $column, $alias); } /** * Query lazily, by chunking the results of a query by comparing IDs in descending order. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) { return $this->orderedLazyById($chunkSize, $column, $alias, true); } /** * Query lazily, by chunking the results of a query by comparing IDs in a given order. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @param bool $descending * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false) { if ($chunkSize < 1) { throw new InvalidArgumentException('The chunk size should be at least 1'); } $column ??= $this->defaultKeyName(); $alias ??= $column; return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) { $lastId = null; while (true) { $clone = clone $this; if ($descending) { $results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get(); } else { $results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get(); } foreach ($results as $result) { yield $result; } if ($results->count() < $chunkSize) { return; } $lastId = $results->last()->{$alias}; if ($lastId === null) { throw new RuntimeException("The lazyById operation was aborted because the [{$alias}] column is not present in the query result."); } } }); } /** * Execute the query and get the first result. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|object|static|null */ public function first($columns = ['*']) { return $this->take(1)->get($columns)->first(); } /** * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|object|static|null * * @throws \Illuminate\Database\RecordsNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function sole($columns = ['*']) { $result = $this->take(2)->get($columns); $count = $result->count(); if ($count === 0) { throw new RecordsNotFoundException; } if ($count > 1) { throw new MultipleRecordsFoundException($count); } return $result->first(); } /** * Paginate the given query using a cursor paginator. * * @param int $perPage * @param array|string $columns * @param string $cursorName * @param \Illuminate\Pagination\Cursor|string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator */ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { if (! $cursor instanceof Cursor) { $cursor = is_string($cursor) ? Cursor::fromEncoded($cursor) : CursorPaginator::resolveCurrentCursor($cursorName, $cursor); } $orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems()); if (! is_null($cursor)) { $addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) { $unionBuilders = isset($builder->unions) ? collect($builder->unions)->pluck('query') : collect(); if (! is_null($previousColumn)) { $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn); $builder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, '=', $cursor->parameter($previousColumn) ); $unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) { $unionBuilder->where( $this->getOriginalColumnNameForCursorPagination($this, $previousColumn), '=', $cursor->parameter($previousColumn) ); $this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); }); } $builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) { ['column' => $column, 'direction' => $direction] = $orders[$i]; $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column); $builder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, $direction === 'asc' ? '>' : '<', $cursor->parameter($column) ); if ($i < $orders->count() - 1) { $builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) { $addCursorConditions($builder, $column, $i + 1); }); } $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { $unionBuilder->where( $this->getOriginalColumnNameForCursorPagination($this, $column), $direction === 'asc' ? '>' : '<', $cursor->parameter($column) ); if ($i < $orders->count() - 1) { $unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) { $addCursorConditions($builder, $column, $i + 1); }); } $this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); }); }); }); }; $addCursorConditions($this, null, 0); } $this->limit($perPage + 1); return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [ 'path' => Paginator::resolveCurrentPath(), 'cursorName' => $cursorName, 'parameters' => $orders->pluck('column')->toArray(), ]); } /** * Get the original column name of the given column, without any aliasing. * * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder * @param string $parameter * @return string */ protected function getOriginalColumnNameForCursorPagination($builder, string $parameter) { $columns = $builder instanceof Builder ? $builder->getQuery()->getColumns() : $builder->getColumns(); if (! is_null($columns)) { foreach ($columns as $column) { if (($position = strripos($column, ' as ')) !== false) { $original = substr($column, 0, $position); $alias = substr($column, $position + 4); if ($parameter === $alias || $builder->getGrammar()->wrap($parameter) === $alias) { return $original; } } } } return $parameter; } /** * Create a new length-aware paginator instance. * * @param \Illuminate\Support\Collection $items * @param int $total * @param int $perPage * @param int $currentPage * @param array $options * @return \Illuminate\Pagination\LengthAwarePaginator */ protected function paginator($items, $total, $perPage, $currentPage, $options) { return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact( 'items', 'total', 'perPage', 'currentPage', 'options' )); } /** * Create a new simple paginator instance. * * @param \Illuminate\Support\Collection $items * @param int $perPage * @param int $currentPage * @param array $options * @return \Illuminate\Pagination\Paginator */ protected function simplePaginator($items, $perPage, $currentPage, $options) { return Container::getInstance()->makeWith(Paginator::class, compact( 'items', 'perPage', 'currentPage', 'options' )); } /** * Create a new cursor paginator instance. * * @param \Illuminate\Support\Collection $items * @param int $perPage * @param \Illuminate\Pagination\Cursor $cursor * @param array $options * @return \Illuminate\Pagination\CursorPaginator */ protected function cursorPaginator($items, $perPage, $cursor, $options) { return Container::getInstance()->makeWith(CursorPaginator::class, compact( 'items', 'perPage', 'cursor', 'options' )); } /** * Pass the query to a given callback. * * @param callable $callback * @return $this */ public function tap($callback) { $callback($this); return $this; } } Concerns/CompilesJsonPaths.php000064400000003007147536122140012441 0ustar00', $column, 2); $field = $this->wrap($parts[0]); $path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1], '->') : ''; return [$field, $path]; } /** * Wrap the given JSON path. * * @param string $value * @param string $delimiter * @return string */ protected function wrapJsonPath($value, $delimiter = '->') { $value = preg_replace("/([\\\\]+)?\\'/", "''", $value); $jsonPath = collect(explode($delimiter, $value)) ->map(fn ($segment) => $this->wrapJsonPathSegment($segment)) ->join('.'); return "'$".(str_starts_with($jsonPath, '[') ? '' : '.').$jsonPath."'"; } /** * Wrap the given JSON path segment. * * @param string $segment * @return string */ protected function wrapJsonPathSegment($segment) { if (preg_match('/(\[[^\]]+\])+$/', $segment, $parts)) { $key = Str::beforeLast($segment, $parts[0]); if (! empty($key)) { return '"'.$key.'"'.$parts[0]; } return $parts[0]; } return '"'.$segment.'"'; } } Concerns/ExplainsQueries.php000064400000000704147536122140012156 0ustar00toSql(); $bindings = $this->getBindings(); $explanation = $this->getConnection()->select('EXPLAIN '.$sql, $bindings); return new Collection($explanation); } } Concerns/ManagesTransactions.php000064400000022751147536122140013007 0ustar00beginTransaction(); // We'll simply execute the given callback within a try / catch block and if we // catch any exception we can rollback this transaction so that none of this // gets actually persisted to a database or stored in a permanent fashion. try { $callbackResult = $callback($this); } // If we catch an exception we'll rollback this transaction and try again if we // are not out of attempts. If we are out of attempts we will just throw the // exception back out, and let the developer handle an uncaught exception. catch (Throwable $e) { $this->handleTransactionException( $e, $currentAttempt, $attempts ); continue; } try { if ($this->transactions == 1) { $this->fireConnectionEvent('committing'); $this->getPdo()->commit(); } $this->transactions = max(0, $this->transactions - 1); if ($this->afterCommitCallbacksShouldBeExecuted()) { $this->transactionsManager?->commit($this->getName()); } } catch (Throwable $e) { $this->handleCommitTransactionException( $e, $currentAttempt, $attempts ); continue; } $this->fireConnectionEvent('committed'); return $callbackResult; } } /** * Handle an exception encountered when running a transacted statement. * * @param \Throwable $e * @param int $currentAttempt * @param int $maxAttempts * @return void * * @throws \Throwable */ protected function handleTransactionException(Throwable $e, $currentAttempt, $maxAttempts) { // On a deadlock, MySQL rolls back the entire transaction so we can't just // retry the query. We have to throw this exception all the way out and // let the developer handle it in another way. We will decrement too. if ($this->causedByConcurrencyError($e) && $this->transactions > 1) { $this->transactions--; $this->transactionsManager?->rollback( $this->getName(), $this->transactions ); throw new DeadlockException($e->getMessage(), is_int($e->getCode()) ? $e->getCode() : 0, $e); } // If there was an exception we will rollback this transaction and then we // can check if we have exceeded the maximum attempt count for this and // if we haven't we will return and try this query again in our loop. $this->rollBack(); if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) { return; } throw $e; } /** * Start a new database transaction. * * @return void * * @throws \Throwable */ public function beginTransaction() { $this->createTransaction(); $this->transactions++; $this->transactionsManager?->begin( $this->getName(), $this->transactions ); $this->fireConnectionEvent('beganTransaction'); } /** * Create a transaction within the database. * * @return void * * @throws \Throwable */ protected function createTransaction() { if ($this->transactions == 0) { $this->reconnectIfMissingConnection(); try { $this->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->transactionLevel() == 1) { $this->fireConnectionEvent('committing'); $this->getPdo()->commit(); } $this->transactions = max(0, $this->transactions - 1); if ($this->afterCommitCallbacksShouldBeExecuted()) { $this->transactionsManager?->commit($this->getName()); } $this->fireConnectionEvent('committed'); } /** * Determine if after commit callbacks should be executed. * * @return bool */ protected function afterCommitCallbacksShouldBeExecuted() { return $this->transactionsManager?->afterCommitCallbacksShouldBeExecuted($this->transactions) || $this->transactions == 0; } /** * Handle an exception encountered when committing a transaction. * * @param \Throwable $e * @param int $currentAttempt * @param int $maxAttempts * @return void * * @throws \Throwable */ protected function handleCommitTransactionException(Throwable $e, $currentAttempt, $maxAttempts) { $this->transactions = max(0, $this->transactions - 1); if ($this->causedByConcurrencyError($e) && $currentAttempt < $maxAttempts) { return; } if ($this->causedByLostConnection($e)) { $this->transactions = 0; } throw $e; } /** * Rollback the active database transaction. * * @param int|null $toLevel * @return void * * @throws \Throwable */ public function rollBack($toLevel = null) { // We allow developers to rollback to a certain transaction level. We will verify // that this given transaction level is valid before attempting to rollback to // that level. If it's not we will just return out and not attempt anything. $toLevel = is_null($toLevel) ? $this->transactions - 1 : $toLevel; if ($toLevel < 0 || $toLevel >= $this->transactions) { return; } // Next, we will actually perform this rollback within this database and fire the // rollback event. We will also set the current transaction level to the given // level that was passed into this method so it will be right from here out. try { $this->performRollBack($toLevel); } catch (Throwable $e) { $this->handleRollBackException($e); } $this->transactions = $toLevel; $this->transactionsManager?->rollback( $this->getName(), $this->transactions ); $this->fireConnectionEvent('rollingBack'); } /** * Perform a rollback within the database. * * @param int $toLevel * @return void * * @throws \Throwable */ protected function performRollBack($toLevel) { if ($toLevel == 0) { $pdo = $this->getPdo(); if ($pdo->inTransaction()) { $pdo->rollBack(); } } elseif ($this->queryGrammar->supportsSavepoints()) { $this->getPdo()->exec( $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1)) ); } } /** * Handle an exception from a rollback. * * @param \Throwable $e * @return void * * @throws \Throwable */ protected function handleRollBackException(Throwable $e) { if ($this->causedByLostConnection($e)) { $this->transactions = 0; $this->transactionsManager?->rollback( $this->getName(), $this->transactions ); } throw $e; } /** * Get the number of active transactions. * * @return int */ public function transactionLevel() { return $this->transactions; } /** * Execute the callback after a transaction commits. * * @param callable $callback * @return void * * @throws \RuntimeException */ public function afterCommit($callback) { if ($this->transactionsManager) { return $this->transactionsManager->addCallback($callback); } throw new RuntimeException('Transactions Manager has not been set.'); } } Concerns/ParsesSearchPath.php000064400000001102147536122140012226 0ustar00 */ protected $doctrineTypeMappings = []; /** * The connection resolvers. * * @var \Closure[] */ protected static $resolvers = []; /** * Create a new database connection instance. * * @param \PDO|\Closure $pdo * @param string $database * @param string $tablePrefix * @param array $config * @return void */ public function __construct($pdo, $database = '', $tablePrefix = '', array $config = []) { $this->pdo = $pdo; // First we will setup the default properties. We keep track of the DB // name we are connected to since it is needed when some reflective // type commands are run such as checking whether a table exists. $this->database = $database; $this->tablePrefix = $tablePrefix; $this->config = $config; // We need to initialize a query grammar and the query post processors // which are both very important parts of the database abstractions // so we initialize these to their default values while starting. $this->useDefaultQueryGrammar(); $this->useDefaultPostProcessor(); } /** * Set the query grammar to the default implementation. * * @return void */ public function useDefaultQueryGrammar() { $this->queryGrammar = $this->getDefaultQueryGrammar(); } /** * Get the default query grammar instance. * * @return \Illuminate\Database\Query\Grammars\Grammar */ protected function getDefaultQueryGrammar() { ($grammar = new QueryGrammar)->setConnection($this); return $grammar; } /** * Set the schema grammar to the default implementation. * * @return void */ public function useDefaultSchemaGrammar() { $this->schemaGrammar = $this->getDefaultSchemaGrammar(); } /** * Get the default schema grammar instance. * * @return \Illuminate\Database\Schema\Grammars\Grammar|null */ protected function getDefaultSchemaGrammar() { // } /** * Set the query post processor to the default implementation. * * @return void */ public function useDefaultPostProcessor() { $this->postProcessor = $this->getDefaultPostProcessor(); } /** * Get the default post processor instance. * * @return \Illuminate\Database\Query\Processors\Processor */ protected function getDefaultPostProcessor() { return new Processor; } /** * Get a schema builder instance for the connection. * * @return \Illuminate\Database\Schema\Builder */ public function getSchemaBuilder() { if (is_null($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } return new SchemaBuilder($this); } /** * Begin a fluent query against a database table. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Contracts\Database\Query\Expression|string $table * @param string|null $as * @return \Illuminate\Database\Query\Builder */ public function table($table, $as = null) { return $this->query()->from($table, $as); } /** * Get a new query builder instance. * * @return \Illuminate\Database\Query\Builder */ public function query() { return new QueryBuilder( $this, $this->getQueryGrammar(), $this->getPostProcessor() ); } /** * Run a select statement and return a single result. * * @param string $query * @param array $bindings * @param bool $useReadPdo * @return mixed */ public function selectOne($query, $bindings = [], $useReadPdo = true) { $records = $this->select($query, $bindings, $useReadPdo); return array_shift($records); } /** * Run a select statement and return the first column of the first row. * * @param string $query * @param array $bindings * @param bool $useReadPdo * @return mixed * * @throws \Illuminate\Database\MultipleColumnsSelectedException */ public function scalar($query, $bindings = [], $useReadPdo = true) { $record = $this->selectOne($query, $bindings, $useReadPdo); if (is_null($record)) { return null; } $record = (array) $record; if (count($record) > 1) { throw new MultipleColumnsSelectedException; } return reset($record); } /** * Run a select statement against the database. * * @param string $query * @param array $bindings * @return array */ public function selectFromWriteConnection($query, $bindings = []) { return $this->select($query, $bindings, false); } /** * Run a select statement against the database. * * @param string $query * @param array $bindings * @param bool $useReadPdo * @return array */ public function select($query, $bindings = [], $useReadPdo = true) { return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) { if ($this->pretending()) { return []; } // For select statements, we'll simply execute the query and return an array // of the database result set. Each element in the array will be a single // row from the database table, and will either be an array or objects. $statement = $this->prepared( $this->getPdoForSelect($useReadPdo)->prepare($query) ); $this->bindValues($statement, $this->prepareBindings($bindings)); $statement->execute(); return $statement->fetchAll(); }); } /** * Run a select statement against the database and returns all of the result sets. * * @param string $query * @param array $bindings * @param bool $useReadPdo * @return array */ public function selectResultSets($query, $bindings = [], $useReadPdo = true) { return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) { if ($this->pretending()) { return []; } $statement = $this->prepared( $this->getPdoForSelect($useReadPdo)->prepare($query) ); $this->bindValues($statement, $this->prepareBindings($bindings)); $statement->execute(); $sets = []; do { $sets[] = $statement->fetchAll(); } while ($statement->nextRowset()); return $sets; }); } /** * Run a select statement against the database and returns a generator. * * @param string $query * @param array $bindings * @param bool $useReadPdo * @return \Generator */ public function cursor($query, $bindings = [], $useReadPdo = true) { $statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) { if ($this->pretending()) { return []; } // First we will create a statement for the query. Then, we will set the fetch // mode and prepare the bindings for the query. Once that's done we will be // ready to execute the query against the database and return the cursor. $statement = $this->prepared($this->getPdoForSelect($useReadPdo) ->prepare($query)); $this->bindValues( $statement, $this->prepareBindings($bindings) ); // Next, we'll execute the query against the database and return the statement // so we can return the cursor. The cursor will use a PHP generator to give // back one row at a time without using a bunch of memory to render them. $statement->execute(); return $statement; }); while ($record = $statement->fetch()) { yield $record; } } /** * Configure the PDO prepared statement. * * @param \PDOStatement $statement * @return \PDOStatement */ protected function prepared(PDOStatement $statement) { $statement->setFetchMode($this->fetchMode); $this->event(new StatementPrepared($this, $statement)); return $statement; } /** * Get the PDO connection to use for a select query. * * @param bool $useReadPdo * @return \PDO */ protected function getPdoForSelect($useReadPdo = true) { return $useReadPdo ? $this->getReadPdo() : $this->getPdo(); } /** * Run an insert statement against the database. * * @param string $query * @param array $bindings * @return bool */ public function insert($query, $bindings = []) { return $this->statement($query, $bindings); } /** * Run an update statement against the database. * * @param string $query * @param array $bindings * @return int */ public function update($query, $bindings = []) { return $this->affectingStatement($query, $bindings); } /** * Run a delete statement against the database. * * @param string $query * @param array $bindings * @return int */ public function delete($query, $bindings = []) { return $this->affectingStatement($query, $bindings); } /** * Execute an SQL statement and return the boolean result. * * @param string $query * @param array $bindings * @return bool */ public function statement($query, $bindings = []) { return $this->run($query, $bindings, function ($query, $bindings) { if ($this->pretending()) { return true; } $statement = $this->getPdo()->prepare($query); $this->bindValues($statement, $this->prepareBindings($bindings)); $this->recordsHaveBeenModified(); return $statement->execute(); }); } /** * Run an SQL statement and get the number of rows affected. * * @param string $query * @param array $bindings * @return int */ public function affectingStatement($query, $bindings = []) { return $this->run($query, $bindings, function ($query, $bindings) { if ($this->pretending()) { return 0; } // For update or delete statements, we want to get the number of rows affected // by the statement and return that back to the developer. We'll first need // to execute the statement and then we'll use PDO to fetch the affected. $statement = $this->getPdo()->prepare($query); $this->bindValues($statement, $this->prepareBindings($bindings)); $statement->execute(); $this->recordsHaveBeenModified( ($count = $statement->rowCount()) > 0 ); return $count; }); } /** * Run a raw, unprepared query against the PDO connection. * * @param string $query * @return bool */ public function unprepared($query) { return $this->run($query, [], function ($query) { if ($this->pretending()) { return true; } $this->recordsHaveBeenModified( $change = $this->getPdo()->exec($query) !== false ); return $change; }); } /** * Execute the given callback in "dry run" mode. * * @param \Closure $callback * @return array */ public function pretend(Closure $callback) { return $this->withFreshQueryLog(function () use ($callback) { $this->pretending = true; // Basically to make the database connection "pretend", we will just return // the default values for all the query methods, then we will return an // array of queries that were "executed" within the Closure callback. $callback($this); $this->pretending = false; return $this->queryLog; }); } /** * Execute the given callback in "dry run" mode. * * @param \Closure $callback * @return array */ protected function withFreshQueryLog($callback) { $loggingQueries = $this->loggingQueries; // First we will back up the value of the logging queries property and then // we'll be ready to run callbacks. This query log will also get cleared // so we will have a new log of all the queries that are executed now. $this->enableQueryLog(); $this->queryLog = []; // Now we'll execute this callback and capture the result. Once it has been // executed we will restore the value of query logging and give back the // value of the callback so the original callers can have the results. $result = $callback(); $this->loggingQueries = $loggingQueries; return $result; } /** * Bind values to their parameters in the given statement. * * @param \PDOStatement $statement * @param array $bindings * @return void */ public function bindValues($statement, $bindings) { foreach ($bindings as $key => $value) { $statement->bindValue( is_string($key) ? $key : $key + 1, $value, match (true) { is_int($value) => PDO::PARAM_INT, is_resource($value) => PDO::PARAM_LOB, default => PDO::PARAM_STR }, ); } } /** * Prepare the query bindings for execution. * * @param array $bindings * @return array */ public function prepareBindings(array $bindings) { $grammar = $this->getQueryGrammar(); foreach ($bindings as $key => $value) { // We need to transform all instances of DateTimeInterface into the actual // date string. Each query grammar maintains its own date string format // so we'll just ask the grammar for the format to get from the date. if ($value instanceof DateTimeInterface) { $bindings[$key] = $value->format($grammar->getDateFormat()); } elseif (is_bool($value)) { $bindings[$key] = (int) $value; } } return $bindings; } /** * Run a SQL statement and log its execution context. * * @param string $query * @param array $bindings * @param \Closure $callback * @return mixed * * @throws \Illuminate\Database\QueryException */ protected function run($query, $bindings, Closure $callback) { foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) { $beforeExecutingCallback($query, $bindings, $this); } $this->reconnectIfMissingConnection(); $start = microtime(true); // Here we will run this query. If an exception occurs we'll determine if it was // caused by a connection that has been lost. If that is the cause, we'll try // to re-establish connection and re-run the query with a fresh connection. try { $result = $this->runQueryCallback($query, $bindings, $callback); } catch (QueryException $e) { $result = $this->handleQueryException( $e, $query, $bindings, $callback ); } // Once we have run the query we will calculate the time that it took to run and // then log the query, bindings, and execution time so we will report them on // the event that the developer needs them. We'll log time in milliseconds. $this->logQuery( $query, $bindings, $this->getElapsedTime($start) ); return $result; } /** * Run a SQL statement. * * @param string $query * @param array $bindings * @param \Closure $callback * @return mixed * * @throws \Illuminate\Database\QueryException */ protected function runQueryCallback($query, $bindings, Closure $callback) { // To execute the statement, we'll simply call the callback, which will actually // run the SQL against the PDO connection. Then we can calculate the time it // took to execute and log the query SQL, bindings and time in our memory. try { return $callback($query, $bindings); } // If an exception occurs when attempting to run a query, we'll format the error // message to include the bindings with SQL, which will make this exception a // lot more helpful to the developer instead of just the database's errors. catch (Exception $e) { if ($this->isUniqueConstraintError($e)) { throw new UniqueConstraintViolationException( $this->getName(), $query, $this->prepareBindings($bindings), $e ); } throw new QueryException( $this->getName(), $query, $this->prepareBindings($bindings), $e ); } } /** * Determine if the given database exception was caused by a unique constraint violation. * * @param \Exception $exception * @return bool */ protected function isUniqueConstraintError(Exception $exception) { return false; } /** * Log a query in the connection's query log. * * @param string $query * @param array $bindings * @param float|null $time * @return void */ public function logQuery($query, $bindings, $time = null) { $this->totalQueryDuration += $time ?? 0.0; $this->event(new QueryExecuted($query, $bindings, $time, $this)); if ($this->loggingQueries) { $this->queryLog[] = compact('query', 'bindings', 'time'); } } /** * Get the elapsed time since a given starting point. * * @param int $start * @return float */ protected function getElapsedTime($start) { return round((microtime(true) - $start) * 1000, 2); } /** * Register a callback to be invoked when the connection queries for longer than a given amount of time. * * @param \DateTimeInterface|\Carbon\CarbonInterval|float|int $threshold * @param callable $handler * @return void */ public function whenQueryingForLongerThan($threshold, $handler) { $threshold = $threshold instanceof DateTimeInterface ? $this->secondsUntil($threshold) * 1000 : $threshold; $threshold = $threshold instanceof CarbonInterval ? $threshold->totalMilliseconds : $threshold; $this->queryDurationHandlers[] = [ 'has_run' => false, 'handler' => $handler, ]; $key = count($this->queryDurationHandlers) - 1; $this->listen(function ($event) use ($threshold, $handler, $key) { if (! $this->queryDurationHandlers[$key]['has_run'] && $this->totalQueryDuration() > $threshold) { $handler($this, $event); $this->queryDurationHandlers[$key]['has_run'] = true; } }); } /** * Allow all the query duration handlers to run again, even if they have already run. * * @return void */ public function allowQueryDurationHandlersToRunAgain() { foreach ($this->queryDurationHandlers as $key => $queryDurationHandler) { $this->queryDurationHandlers[$key]['has_run'] = false; } } /** * Get the duration of all run queries in milliseconds. * * @return float */ public function totalQueryDuration() { return $this->totalQueryDuration; } /** * Reset the duration of all run queries. * * @return void */ public function resetTotalQueryDuration() { $this->totalQueryDuration = 0.0; } /** * Handle a query exception. * * @param \Illuminate\Database\QueryException $e * @param string $query * @param array $bindings * @param \Closure $callback * @return mixed * * @throws \Illuminate\Database\QueryException */ protected function handleQueryException(QueryException $e, $query, $bindings, Closure $callback) { if ($this->transactions >= 1) { throw $e; } return $this->tryAgainIfCausedByLostConnection( $e, $query, $bindings, $callback ); } /** * Handle a query exception that occurred during query execution. * * @param \Illuminate\Database\QueryException $e * @param string $query * @param array $bindings * @param \Closure $callback * @return mixed * * @throws \Illuminate\Database\QueryException */ protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback) { if ($this->causedByLostConnection($e->getPrevious())) { $this->reconnect(); return $this->runQueryCallback($query, $bindings, $callback); } throw $e; } /** * Reconnect to the database. * * @return mixed|false * * @throws \Illuminate\Database\LostConnectionException */ public function reconnect() { if (is_callable($this->reconnector)) { $this->doctrineConnection = null; return call_user_func($this->reconnector, $this); } throw new LostConnectionException('Lost connection and no reconnector available.'); } /** * Reconnect to the database if a PDO connection is missing. * * @return void */ public function reconnectIfMissingConnection() { if (is_null($this->pdo)) { $this->reconnect(); } } /** * Disconnect from the underlying PDO connection. * * @return void */ public function disconnect() { $this->setPdo(null)->setReadPdo(null); $this->doctrineConnection = null; } /** * Register a hook to be run just before a database query is executed. * * @param \Closure $callback * @return $this */ public function beforeExecuting(Closure $callback) { $this->beforeExecutingCallbacks[] = $callback; return $this; } /** * Register a database query listener with the connection. * * @param \Closure $callback * @return void */ public function listen(Closure $callback) { $this->events?->listen(Events\QueryExecuted::class, $callback); } /** * Fire an event for this connection. * * @param string $event * @return array|null */ protected function fireConnectionEvent($event) { return $this->events?->dispatch(match ($event) { 'beganTransaction' => new TransactionBeginning($this), 'committed' => new TransactionCommitted($this), 'committing' => new TransactionCommitting($this), 'rollingBack' => new TransactionRolledBack($this), default => null, }); } /** * Fire the given event if possible. * * @param mixed $event * @return void */ protected function event($event) { $this->events?->dispatch($event); } /** * Get a new raw query expression. * * @param mixed $value * @return \Illuminate\Contracts\Database\Query\Expression */ public function raw($value) { return new Expression($value); } /** * Escape a value for safe SQL embedding. * * @param string|float|int|bool|null $value * @param bool $binary * @return string */ public function escape($value, $binary = false) { if ($value === null) { return 'null'; } elseif ($binary) { return $this->escapeBinary($value); } elseif (is_int($value) || is_float($value)) { return (string) $value; } elseif (is_bool($value)) { return $this->escapeBool($value); } else { if (str_contains($value, "\00")) { throw new RuntimeException('Strings with null bytes cannot be escaped. Use the binary escape option.'); } if (preg_match('//u', $value) === false) { throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.'); } return $this->escapeString($value); } } /** * Escape a string value for safe SQL embedding. * * @param string $value * @return string */ protected function escapeString($value) { return $this->getReadPdo()->quote($value); } /** * Escape a boolean value for safe SQL embedding. * * @param bool $value * @return string */ protected function escapeBool($value) { return $value ? '1' : '0'; } /** * Escape a binary value for safe SQL embedding. * * @param string $value * @return string */ protected function escapeBinary($value) { throw new RuntimeException('The database connection does not support escaping binary values.'); } /** * Determine if the database connection has modified any database records. * * @return bool */ public function hasModifiedRecords() { return $this->recordsModified; } /** * Indicate if any records have been modified. * * @param bool $value * @return void */ public function recordsHaveBeenModified($value = true) { if (! $this->recordsModified) { $this->recordsModified = $value; } } /** * Set the record modification state. * * @param bool $value * @return $this */ public function setRecordModificationState(bool $value) { $this->recordsModified = $value; return $this; } /** * Reset the record modification state. * * @return void */ public function forgetRecordModificationState() { $this->recordsModified = false; } /** * Indicate that the connection should use the write PDO connection for reads. * * @param bool $value * @return $this */ public function useWriteConnectionWhenReading($value = true) { $this->readOnWriteConnection = $value; return $this; } /** * Is Doctrine available? * * @return bool */ public function isDoctrineAvailable() { return class_exists('Doctrine\DBAL\Connection'); } /** * Indicates whether native alter operations will be used when dropping, renaming, or modifying columns, even if Doctrine DBAL is installed. * * @return bool */ public function usingNativeSchemaOperations() { return ! $this->isDoctrineAvailable() || SchemaBuilder::$alwaysUsesNativeSchemaOperationsIfPossible; } /** * Get a Doctrine Schema Column instance. * * @param string $table * @param string $column * @return \Doctrine\DBAL\Schema\Column */ public function getDoctrineColumn($table, $column) { $schema = $this->getDoctrineSchemaManager(); return $schema->introspectTable($table)->getColumn($column); } /** * Get the Doctrine DBAL schema manager for the connection. * * @return \Doctrine\DBAL\Schema\AbstractSchemaManager */ public function getDoctrineSchemaManager() { $connection = $this->getDoctrineConnection(); return $connection->createSchemaManager(); } /** * Get the Doctrine DBAL database connection instance. * * @return \Doctrine\DBAL\Connection */ public function getDoctrineConnection() { if (is_null($this->doctrineConnection)) { $driver = $this->getDoctrineDriver(); $this->doctrineConnection = new DoctrineConnection(array_filter([ 'pdo' => $this->getPdo(), 'dbname' => $this->getDatabaseName(), 'driver' => $driver->getName(), 'serverVersion' => $this->getConfig('server_version'), ]), $driver); foreach ($this->doctrineTypeMappings as $name => $type) { $this->doctrineConnection ->getDatabasePlatform() ->registerDoctrineTypeMapping($type, $name); } } return $this->doctrineConnection; } /** * Register a custom Doctrine mapping type. * * @param Type|class-string $class * @param string $name * @param string $type * @return void * * @throws \Doctrine\DBAL\Exception * @throws \RuntimeException */ public function registerDoctrineType(Type|string $class, string $name, string $type): void { if (! $this->isDoctrineAvailable()) { throw new RuntimeException( 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' ); } if (! Type::hasType($name)) { Type::getTypeRegistry() ->register($name, is_string($class) ? new $class() : $class); } $this->doctrineTypeMappings[$name] = $type; } /** * Get the current PDO connection. * * @return \PDO */ public function getPdo() { if ($this->pdo instanceof Closure) { return $this->pdo = call_user_func($this->pdo); } return $this->pdo; } /** * Get the current PDO connection parameter without executing any reconnect logic. * * @return \PDO|\Closure|null */ public function getRawPdo() { return $this->pdo; } /** * Get the current PDO connection used for reading. * * @return \PDO */ public function getReadPdo() { if ($this->transactions > 0) { return $this->getPdo(); } if ($this->readOnWriteConnection || ($this->recordsModified && $this->getConfig('sticky'))) { return $this->getPdo(); } if ($this->readPdo instanceof Closure) { return $this->readPdo = call_user_func($this->readPdo); } return $this->readPdo ?: $this->getPdo(); } /** * Get the current read PDO connection parameter without executing any reconnect logic. * * @return \PDO|\Closure|null */ public function getRawReadPdo() { return $this->readPdo; } /** * Set the PDO connection. * * @param \PDO|\Closure|null $pdo * @return $this */ public function setPdo($pdo) { $this->transactions = 0; $this->pdo = $pdo; return $this; } /** * Set the PDO connection used for reading. * * @param \PDO|\Closure|null $pdo * @return $this */ public function setReadPdo($pdo) { $this->readPdo = $pdo; return $this; } /** * Set the reconnect instance on the connection. * * @param callable $reconnector * @return $this */ public function setReconnector(callable $reconnector) { $this->reconnector = $reconnector; return $this; } /** * Get the database connection name. * * @return string|null */ public function getName() { return $this->getConfig('name'); } /** * Get the database connection full name. * * @return string|null */ public function getNameWithReadWriteType() { return $this->getName().($this->readWriteType ? '::'.$this->readWriteType : ''); } /** * Get an option from the configuration options. * * @param string|null $option * @return mixed */ public function getConfig($option = null) { return Arr::get($this->config, $option); } /** * Get the PDO driver name. * * @return string */ public function getDriverName() { return $this->getConfig('driver'); } /** * Get the query grammar used by the connection. * * @return \Illuminate\Database\Query\Grammars\Grammar */ public function getQueryGrammar() { return $this->queryGrammar; } /** * Set the query grammar used by the connection. * * @param \Illuminate\Database\Query\Grammars\Grammar $grammar * @return $this */ public function setQueryGrammar(Query\Grammars\Grammar $grammar) { $this->queryGrammar = $grammar; return $this; } /** * Get the schema grammar used by the connection. * * @return \Illuminate\Database\Schema\Grammars\Grammar */ public function getSchemaGrammar() { return $this->schemaGrammar; } /** * Set the schema grammar used by the connection. * * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return $this */ public function setSchemaGrammar(Schema\Grammars\Grammar $grammar) { $this->schemaGrammar = $grammar; return $this; } /** * Get the query post processor used by the connection. * * @return \Illuminate\Database\Query\Processors\Processor */ public function getPostProcessor() { return $this->postProcessor; } /** * Set the query post processor used by the connection. * * @param \Illuminate\Database\Query\Processors\Processor $processor * @return $this */ public function setPostProcessor(Processor $processor) { $this->postProcessor = $processor; return $this; } /** * Get the event dispatcher used by the connection. * * @return \Illuminate\Contracts\Events\Dispatcher */ public function getEventDispatcher() { return $this->events; } /** * Set the event dispatcher instance on the connection. * * @param \Illuminate\Contracts\Events\Dispatcher $events * @return $this */ public function setEventDispatcher(Dispatcher $events) { $this->events = $events; return $this; } /** * Unset the event dispatcher for this connection. * * @return void */ public function unsetEventDispatcher() { $this->events = null; } /** * Set the transaction manager instance on the connection. * * @param \Illuminate\Database\DatabaseTransactionsManager $manager * @return $this */ public function setTransactionManager($manager) { $this->transactionsManager = $manager; return $this; } /** * Unset the transaction manager for this connection. * * @return void */ public function unsetTransactionManager() { $this->transactionsManager = null; } /** * Determine if the connection is in a "dry run". * * @return bool */ public function pretending() { return $this->pretending === true; } /** * Get the connection query log. * * @return array */ public function getQueryLog() { return $this->queryLog; } /** * Get the connection query log with embedded bindings. * * @return array */ public function getRawQueryLog() { return array_map(fn (array $log) => [ 'raw_query' => $this->queryGrammar->substituteBindingsIntoRawSql( $log['query'], $this->prepareBindings($log['bindings']) ), 'time' => $log['time'], ], $this->getQueryLog()); } /** * Clear the query log. * * @return void */ public function flushQueryLog() { $this->queryLog = []; } /** * Enable the query log on the connection. * * @return void */ public function enableQueryLog() { $this->loggingQueries = true; } /** * Disable the query log on the connection. * * @return void */ public function disableQueryLog() { $this->loggingQueries = false; } /** * Determine whether we're logging queries. * * @return bool */ public function logging() { return $this->loggingQueries; } /** * Get the name of the connected database. * * @return string */ public function getDatabaseName() { return $this->database; } /** * Set the name of the connected database. * * @param string $database * @return $this */ public function setDatabaseName($database) { $this->database = $database; return $this; } /** * Set the read / write type of the connection. * * @param string|null $readWriteType * @return $this */ public function setReadWriteType($readWriteType) { $this->readWriteType = $readWriteType; return $this; } /** * Get the table prefix for the connection. * * @return string */ public function getTablePrefix() { return $this->tablePrefix; } /** * Set the table prefix in use by the connection. * * @param string $prefix * @return $this */ public function setTablePrefix($prefix) { $this->tablePrefix = $prefix; $this->getQueryGrammar()->setTablePrefix($prefix); return $this; } /** * Set the table prefix and return the grammar. * * @param \Illuminate\Database\Grammar $grammar * @return \Illuminate\Database\Grammar */ public function withTablePrefix(Grammar $grammar) { $grammar->setTablePrefix($this->tablePrefix); return $grammar; } /** * Register a connection resolver. * * @param string $driver * @param \Closure $callback * @return void */ public static function resolverFor($driver, Closure $callback) { static::$resolvers[$driver] = $callback; } /** * Get the connection resolver for the given driver. * * @param string $driver * @return mixed */ public static function getResolver($driver) { return static::$resolvers[$driver] ?? null; } } ConnectionInterface.php000075500000007474147536122140011221 0ustar00 $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; } } ConnectionResolverInterface.php000075500000001077147536122140012734 0ustar00container = $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($this->parseHosts($config)) as $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 fn () => $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); } return match ($config['driver']) { 'mysql' => new MySqlConnector, 'pgsql' => new PostgresConnector, 'sqlite' => new SQLiteConnector, 'sqlsrv' => new SqlServerConnector, default => throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]."), }; } /** * Create a new connection instance. * * @param string $driver * @param \PDO|\Closure $connection * @param string $database * @param string $prefix * @param array $config * @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); } return match ($driver) { 'mysql' => new MySqlConnection($connection, $database, $prefix, $config), 'pgsql' => new PostgresConnection($connection, $database, $prefix, $config), 'sqlite' => new SQLiteConnection($connection, $database, $prefix, $config), 'sqlsrv' => new SqlServerConnection($connection, $database, $prefix, $config), default => throw new InvalidArgumentException("Unsupported driver [{$driver}]."), }; } } Connectors/Connector.php000075500000005537147536122140011346 0ustar00 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) { return new PDO($dsn, $username, $password, $options); } /** * 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; } } Connectors/ConnectorInterface.php000075500000000360147536122140013154 0ustar00getDsn($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'"; } } Connectors/PostgresConnector.php000075500000015032147536122140013064 0ustar00 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->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->configureSearchPath($connection, $config); // Postgres allows an application_name to be set by the user and this name is // used to when monitoring the application with pg_stat_activity. So we'll // determine if the option has been specified and run a statement if so. $this->configureApplicationName($connection, $config); $this->configureSynchronousCommit($connection, $config); return $connection; } /** * Set the connection transaction isolation level. * * @param \PDO $connection * @param array $config * @return void */ protected function configureIsolationLevel($connection, array $config) { if (isset($config['isolation_level'])) { $connection->prepare("set session characteristics as transaction isolation level {$config['isolation_level']}")->execute(); } } /** * Set the connection character set and collation. * * @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 "search_path" on the database connection. * * @param \PDO $connection * @param array $config * @return void */ protected function configureSearchPath($connection, $config) { if (isset($config['search_path']) || isset($config['schema'])) { $searchPath = $this->quoteSearchPath( $this->parseSearchPath($config['search_path'] ?? $config['schema']) ); $connection->prepare("set search_path to {$searchPath}")->execute(); } } /** * Format the search path for the DSN. * * @param array $searchPath * @return string */ protected function quoteSearchPath($searchPath) { return count($searchPath) === 1 ? '"'.$searchPath[0].'"' : '"'.implode('", "', $searchPath).'"'; } /** * Set the application name 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};" : ''; // Sometimes - users may need to connect to a database that has a different // name than the database used for "information_schema" queries. This is // typically the case if using "pgbouncer" type software when pooling. $database = $connect_via_database ?? $database; $port = $connect_via_port ?? $port ?? null; $dsn = "pgsql:{$host}dbname='{$database}'"; // If a port was specified, we will add it to this Postgres DSN connections // format. Once we have done that we are ready to return this connection // string back out for usage, as this has been fully constructed here. if (! is_null($port)) { $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(); } } Connectors/SQLiteConnector.php000075500000002533147536122140012421 0ustar00getOptions($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 SQLiteDatabaseDoesNotExistException($config['database']); } return $this->createConnection("sqlite:{$path}", $config, $options); } } Connectors/SqlServerConnector.php000075500000014711147536122140013207 0ustar00 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); $connection = $this->createConnection($this->getDsn($config), $config, $options); $this->configureIsolationLevel($connection, $config); return $connection; } /** * Set the connection transaction isolation level. * * https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql * * @param \PDO $connection * @param array $config * @return void */ protected function configureIsolationLevel($connection, array $config) { if (! isset($config['isolation_level'])) { return; } $connection->prepare( "SET TRANSACTION ISOLATION LEVEL {$config['isolation_level']}" )->execute(); } /** * Create a DSN string from a configuration. * * @param array $config * @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']; } if (isset($config['authentication'])) { $arguments['Authentication'] = $config['authentication']; } 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(); } } Console/DatabaseInspectionCommand.php000064400000017343147536122140013733 0ustar00 'string', 'citext' => 'string', 'enum' => 'string', 'geometry' => 'string', 'geomcollection' => 'string', 'linestring' => 'string', 'ltree' => 'string', 'multilinestring' => 'string', 'multipoint' => 'string', 'multipolygon' => 'string', 'point' => 'string', 'polygon' => 'string', 'sysname' => 'string', ]; /** * The Composer instance. * * @var \Illuminate\Support\Composer */ protected $composer; /** * Create a new command instance. * * @param \Illuminate\Support\Composer|null $composer * @return void */ public function __construct(Composer $composer = null) { parent::__construct(); $this->composer = $composer ?? $this->laravel->make(Composer::class); } /** * Register the custom Doctrine type mappings for inspection commands. * * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @return void */ protected function registerTypeMappings(AbstractPlatform $platform) { foreach ($this->typeMappings as $type => $value) { $platform->registerDoctrineTypeMapping($type, $value); } } /** * Get a human-readable platform name for the given platform. * * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform * @param string $database * @return string */ protected function getPlatformName(AbstractPlatform $platform, $database) { return match (class_basename($platform)) { 'MySQLPlatform' => 'MySQL <= 5', 'MySQL57Platform' => 'MySQL 5.7', 'MySQL80Platform' => 'MySQL 8', 'PostgreSQL100Platform', 'PostgreSQLPlatform' => 'Postgres', 'SqlitePlatform' => 'SQLite', 'SQLServerPlatform' => 'SQL Server', 'SQLServer2012Platform' => 'SQL Server 2012', default => $database, }; } /** * Get the size of a table in bytes. * * @param \Illuminate\Database\ConnectionInterface $connection * @param string $table * @return int|null */ protected function getTableSize(ConnectionInterface $connection, string $table) { return match (true) { $connection instanceof MySqlConnection => $this->getMySQLTableSize($connection, $table), $connection instanceof PostgresConnection => $this->getPostgresTableSize($connection, $table), $connection instanceof SQLiteConnection => $this->getSqliteTableSize($connection, $table), default => null, }; } /** * Get the size of a MySQL table in bytes. * * @param \Illuminate\Database\ConnectionInterface $connection * @param string $table * @return mixed */ protected function getMySQLTableSize(ConnectionInterface $connection, string $table) { $result = $connection->selectOne('SELECT (data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = ? AND table_name = ?', [ $connection->getDatabaseName(), $table, ]); return Arr::wrap((array) $result)['size']; } /** * Get the size of a Postgres table in bytes. * * @param \Illuminate\Database\ConnectionInterface $connection * @param string $table * @return mixed */ protected function getPostgresTableSize(ConnectionInterface $connection, string $table) { $result = $connection->selectOne('SELECT pg_total_relation_size(?) AS size;', [ $table, ]); return Arr::wrap((array) $result)['size']; } /** * Get the size of a SQLite table in bytes. * * @param \Illuminate\Database\ConnectionInterface $connection * @param string $table * @return mixed */ protected function getSqliteTableSize(ConnectionInterface $connection, string $table) { try { $result = $connection->selectOne('SELECT SUM(pgsize) AS size FROM dbstat WHERE name=?', [ $table, ]); return Arr::wrap((array) $result)['size']; } catch (QueryException) { return null; } } /** * Get the number of open connections for a database. * * @param \Illuminate\Database\ConnectionInterface $connection * @return int|null */ protected function getConnectionCount(ConnectionInterface $connection) { $result = match (true) { $connection instanceof MySqlConnection => $connection->selectOne('show status where variable_name = "threads_connected"'), $connection instanceof PostgresConnection => $connection->selectOne('select count(*) AS "Value" from pg_stat_activity'), $connection instanceof SqlServerConnection => $connection->selectOne('SELECT COUNT(*) Value FROM sys.dm_exec_sessions WHERE status = ?', ['running']), default => null, }; if (! $result) { return null; } return Arr::wrap((array) $result)['Value']; } /** * Get the connection configuration details for the given connection. * * @param string $database * @return array */ protected function getConfigFromDatabase($database) { $database ??= config('database.default'); return Arr::except(config('database.connections.'.$database), ['password']); } /** * Ensure the dependencies for the database commands are available. * * @return bool */ protected function ensureDependenciesExist() { return tap(interface_exists('Doctrine\DBAL\Driver'), function ($dependenciesExist) { if (! $dependenciesExist && confirm('Inspecting database information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?', default: false)) { $this->installDependencies(); } }); } /** * Install the command's dependencies. * * @return void * * @throws \Symfony\Component\Process\Exception\ProcessSignaledException */ protected function installDependencies() { $command = collect($this->composer->findComposer()) ->push('require doctrine/dbal:^3.5.1') ->implode(' '); $process = Process::fromShellCommandline($command, null, null, null, null); if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { try { $process->setTty(true); } catch (RuntimeException $e) { $this->components->warn($e->getMessage()); } } try { $process->run(fn ($type, $line) => $this->output->write($line)); } catch (ProcessSignaledException $e) { if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { throw $e; } } } } Console/DbCommand.php000064400000015026147536122140010514 0ustar00getConnection(); if (! isset($connection['host']) && $connection['driver'] !== 'sqlite') { $this->components->error('No host specified for this database connection.'); $this->line(' Use the [--read] and [--write] options to specify a read or write connection.'); $this->newLine(); return Command::FAILURE; } (new Process( array_merge([$this->getCommand($connection)], $this->commandArguments($connection)), null, $this->commandEnvironment($connection) ))->setTimeout(null)->setTty(true)->mustRun(function ($type, $buffer) { $this->output->write($buffer); }); return 0; } /** * Get the database connection configuration. * * @return array * * @throws \UnexpectedValueException */ public function getConnection() { $connection = $this->laravel['config']['database.connections.'. (($db = $this->argument('connection')) ?? $this->laravel['config']['database.default']) ]; if (empty($connection)) { throw new UnexpectedValueException("Invalid database connection [{$db}]."); } if (! empty($connection['url'])) { $connection = (new ConfigurationUrlParser)->parseConfiguration($connection); } if ($this->option('read')) { if (is_array($connection['read']['host'])) { $connection['read']['host'] = $connection['read']['host'][0]; } $connection = array_merge($connection, $connection['read']); } elseif ($this->option('write')) { if (is_array($connection['write']['host'])) { $connection['write']['host'] = $connection['write']['host'][0]; } $connection = array_merge($connection, $connection['write']); } return $connection; } /** * Get the arguments for the database client command. * * @param array $connection * @return array */ public function commandArguments(array $connection) { $driver = ucfirst($connection['driver']); return $this->{"get{$driver}Arguments"}($connection); } /** * Get the environment variables for the database client command. * * @param array $connection * @return array|null */ public function commandEnvironment(array $connection) { $driver = ucfirst($connection['driver']); if (method_exists($this, "get{$driver}Environment")) { return $this->{"get{$driver}Environment"}($connection); } return null; } /** * Get the database client command to run. * * @param array $connection * @return string */ public function getCommand(array $connection) { return [ 'mysql' => 'mysql', 'pgsql' => 'psql', 'sqlite' => 'sqlite3', 'sqlsrv' => 'sqlcmd', ][$connection['driver']]; } /** * Get the arguments for the MySQL CLI. * * @param array $connection * @return array */ protected function getMysqlArguments(array $connection) { return array_merge([ '--host='.$connection['host'], '--port='.$connection['port'], '--user='.$connection['username'], ], $this->getOptionalArguments([ 'password' => '--password='.$connection['password'], 'unix_socket' => '--socket='.($connection['unix_socket'] ?? ''), 'charset' => '--default-character-set='.($connection['charset'] ?? ''), ], $connection), [$connection['database']]); } /** * Get the arguments for the Postgres CLI. * * @param array $connection * @return array */ protected function getPgsqlArguments(array $connection) { return [$connection['database']]; } /** * Get the arguments for the SQLite CLI. * * @param array $connection * @return array */ protected function getSqliteArguments(array $connection) { return [$connection['database']]; } /** * Get the arguments for the SQL Server CLI. * * @param array $connection * @return array */ protected function getSqlsrvArguments(array $connection) { return array_merge(...$this->getOptionalArguments([ 'database' => ['-d', $connection['database']], 'username' => ['-U', $connection['username']], 'password' => ['-P', $connection['password']], 'host' => ['-S', 'tcp:'.$connection['host'] .($connection['port'] ? ','.$connection['port'] : ''), ], ], $connection)); } /** * Get the environment variables for the Postgres CLI. * * @param array $connection * @return array|null */ protected function getPgsqlEnvironment(array $connection) { return array_merge(...$this->getOptionalArguments([ 'username' => ['PGUSER' => $connection['username']], 'host' => ['PGHOST' => $connection['host']], 'port' => ['PGPORT' => $connection['port']], 'password' => ['PGPASSWORD' => $connection['password']], ], $connection)); } /** * Get the optional arguments based on the connection configuration. * * @param array $args * @param array $connection * @return array */ protected function getOptionalArguments(array $args, array $connection) { return array_values(array_filter($args, function ($key) use ($connection) { return ! empty($connection[$key]); }, ARRAY_FILTER_USE_KEY)); } } Console/DumpCommand.php000064400000005364147536122140011100 0ustar00connection($database = $this->input->getOption('database')); $this->schemaState($connection)->dump( $connection, $path = $this->path($connection) ); $dispatcher->dispatch(new SchemaDumped($connection, $path)); $info = 'Database schema dumped'; if ($this->option('prune')) { (new Filesystem)->deleteDirectory( database_path('migrations'), $preserve = false ); $info .= ' and pruned'; } $this->components->info($info.' successfully.'); } /** * Create a schema state instance for the given connection. * * @param \Illuminate\Database\Connection $connection * @return mixed */ protected function schemaState(Connection $connection) { return $connection->getSchemaState() ->withMigrationTable($connection->getTablePrefix().Config::get('database.migrations', 'migrations')) ->handleOutputUsing(function ($type, $buffer) { $this->output->write($buffer); }); } /** * Get the path that the dump should be written to. * * @param \Illuminate\Database\Connection $connection */ protected function path(Connection $connection) { return tap($this->option('path') ?: database_path('schema/'.$connection->getName().'-schema.sql'), function ($path) { (new Filesystem)->ensureDirectoryExists(dirname($path)); }); } } Console/Factories/FactoryMakeCommand.php000064400000007170147536122140014314 0ustar00resolveStubPath('/stubs/factory.stub'); } /** * Resolve the fully-qualified path to the stub. * * @param string $stub * @return string */ protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) ? $customPath : __DIR__.$stub; } /** * Build the class with the given name. * * @param string $name * @return string */ protected function buildClass($name) { $factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name))); $namespaceModel = $this->option('model') ? $this->qualifyModel($this->option('model')) : $this->qualifyModel($this->guessModelName($name)); $model = class_basename($namespaceModel); $namespace = $this->getNamespace( Str::replaceFirst($this->rootNamespace(), 'Database\\Factories\\', $this->qualifyClass($this->getNameInput())) ); $replace = [ '{{ factoryNamespace }}' => $namespace, 'NamespacedDummyModel' => $namespaceModel, '{{ namespacedModel }}' => $namespaceModel, '{{namespacedModel}}' => $namespaceModel, 'DummyModel' => $model, '{{ model }}' => $model, '{{model}}' => $model, '{{ factory }}' => $factory, '{{factory}}' => $factory, ]; return str_replace( array_keys($replace), array_values($replace), parent::buildClass($name) ); } /** * Get the destination class path. * * @param string $name * @return string */ protected function getPath($name) { $name = (string) Str::of($name)->replaceFirst($this->rootNamespace(), '')->finish('Factory'); return $this->laravel->databasePath().'/factories/'.str_replace('\\', '/', $name).'.php'; } /** * Guess the model name from the Factory name or return a default model name. * * @param string $name * @return string */ protected function guessModelName($name) { if (str_ends_with($name, 'Factory')) { $name = substr($name, 0, -7); } $modelName = $this->qualifyModel(Str::after($name, $this->rootNamespace())); if (class_exists($modelName)) { return $modelName; } if (is_dir(app_path('Models/'))) { return $this->rootNamespace().'Models\Model'; } return $this->rootNamespace().'Model'; } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['model', 'm', InputOption::VALUE_OPTIONAL, 'The name of the model'], ]; } } Console/Factories/stubs/factory.stub000064400000000655147536122140013606 0ustar00 */ class {{ factory }}Factory extends Factory { /** * Define the model's default state. * * @return array */ public function definition(): array { return [ // ]; } } Console/Migrations/BaseCommand.php000075500000002652147536122140013161 0ustar00input->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'; } } Console/Migrations/FreshCommand.php000064400000007314147536122140013353 0ustar00confirmToProceed()) { return 1; } $database = $this->input->getOption('database'); $this->newLine(); $this->components->task('Dropping all tables', fn () => $this->callSilent('db:wipe', array_filter([ '--database' => $database, '--drop-views' => $this->option('drop-views'), '--drop-types' => $this->option('drop-types'), '--force' => true, ])) == 0); $this->newLine(); $this->call('migrate', array_filter([ '--database' => $database, '--path' => $this->input->getOption('path'), '--realpath' => $this->input->getOption('realpath'), '--schema-path' => $this->input->getOption('schema-path'), '--force' => true, '--step' => $this->option('step'), ])); if ($this->laravel->bound(Dispatcher::class)) { $this->laravel[Dispatcher::class]->dispatch( new DatabaseRefreshed($database, $this->needsSeeding()) ); } if ($this->needsSeeding()) { $this->runSeeder($database); } return 0; } /** * Determine if the developer has requested database seeding. * * @return bool */ protected function needsSeeding() { return $this->option('seed') || $this->option('seeder'); } /** * Run the database seeder command. * * @param string $database * @return void */ protected function runSeeder($database) { $this->call('db:seed', array_filter([ '--database' => $database, '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder', '--force' => true, ])); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], ['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'], ['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'], ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], ['schema-path', null, InputOption::VALUE_OPTIONAL, 'The path to a schema dump file'], ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'], ['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'], ['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually'], ]; } } Console/Migrations/InstallCommand.php000075500000003064147536122140013713 0ustar00repository = $repository; } /** * Execute the console command. * * @return void */ public function handle() { $this->repository->setSource($this->input->getOption('database')); $this->repository->createRepository(); $this->components->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'], ]; } } Console/Migrations/MigrateCommand.php000075500000022421147536122140013673 0ustar00migrator = $migrator; $this->dispatcher = $dispatcher; } /** * Execute the console command. * * @return int */ public function handle() { if (! $this->confirmToProceed()) { return 1; } $this->migrator->usingConnection($this->option('database'), function () { $this->prepareDatabase(); // Next, we will check to see if a path option has been defined. If it has // we will use the path relative to the root of this installation folder // so that migrations may be run for any path within the applications. $this->migrator->setOutput($this->output) ->run($this->getMigrationPaths(), [ 'pretend' => $this->option('pretend'), 'step' => $this->option('step'), ]); // Finally, if the "seed" option has been given, we will re-run the database // seed task to re-populate the database, which is convenient when adding // a migration and a seed at the same time, as it is only this command. if ($this->option('seed') && ! $this->option('pretend')) { $this->call('db:seed', [ '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder', '--force' => true, ]); } }); return 0; } /** * Prepare the migration database for running. * * @return void */ protected function prepareDatabase() { if (! $this->repositoryExists()) { $this->components->info('Preparing database.'); $this->components->task('Creating migration table', function () { return $this->callSilent('migrate:install', array_filter([ '--database' => $this->option('database'), ])) == 0; }); $this->newLine(); } if (! $this->migrator->hasRunAnyMigrations() && ! $this->option('pretend')) { $this->loadSchemaState(); } } /** * Determine if the migrator repository exists. * * @return bool */ protected function repositoryExists() { return retry(2, fn () => $this->migrator->repositoryExists(), 0, function ($e) { try { if ($e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { return $this->createMissingSqliteDatabase($e->getPrevious()->path); } $connection = $this->migrator->resolveConnection($this->option('database')); if ( $e->getPrevious() instanceof PDOException && $e->getPrevious()->getCode() === 1049 && $connection->getDriverName() === 'mysql') { return $this->createMissingMysqlDatabase($connection); } return false; } catch (Throwable) { return false; } }); } /** * Create a missing SQLite database. * * @param string $path * @return bool */ protected function createMissingSqliteDatabase($path) { if ($this->option('force')) { return touch($path); } if ($this->option('no-interaction')) { return false; } $this->components->warn('The SQLite database does not exist: '.$path); if (! confirm('Would you like to create it?', default: false)) { return false; } return touch($path); } /** * Create a missing MySQL database. * * @return bool */ protected function createMissingMysqlDatabase($connection) { if ($this->laravel['config']->get("database.connections.{$connection->getName()}.database") !== $connection->getDatabaseName()) { return false; } if (! $this->option('force') && $this->option('no-interaction')) { return false; } if (! $this->option('force') && ! $this->option('no-interaction')) { $this->components->warn("The database '{$connection->getDatabaseName()}' does not exist on the '{$connection->getName()}' connection."); if (! confirm('Would you like to create it?', default: false)) { return false; } } try { $this->laravel['config']->set("database.connections.{$connection->getName()}.database", null); $this->laravel['db']->purge(); $freshConnection = $this->migrator->resolveConnection($this->option('database')); return tap($freshConnection->unprepared("CREATE DATABASE IF NOT EXISTS `{$connection->getDatabaseName()}`"), function () { $this->laravel['db']->purge(); }); } finally { $this->laravel['config']->set("database.connections.{$connection->getName()}.database", $connection->getDatabaseName()); } } /** * Load the schema state to seed the initial database schema structure. * * @return void */ protected function loadSchemaState() { $connection = $this->migrator->resolveConnection($this->option('database')); // First, we will make sure that the connection supports schema loading and that // the schema file exists before we proceed any further. If not, we will just // continue with the standard migration operation as normal without errors. if ($connection instanceof SqlServerConnection || ! is_file($path = $this->schemaPath($connection))) { return; } $this->components->info('Loading stored database schemas.'); $this->components->task($path, function () use ($connection, $path) { // Since the schema file will create the "migrations" table and reload it to its // proper state, we need to delete it here so we don't get an error that this // table already exists when the stored database schema file gets executed. $this->migrator->deleteRepository(); $connection->getSchemaState()->handleOutputUsing(function ($type, $buffer) { $this->output->write($buffer); })->load($path); }); $this->newLine(); // Finally, we will fire an event that this schema has been loaded so developers // can perform any post schema load tasks that are necessary in listeners for // this event, which may seed the database tables with some necessary data. $this->dispatcher->dispatch( new SchemaLoaded($connection, $path) ); } /** * Get the path to the stored schema for the given connection. * * @param \Illuminate\Database\Connection $connection * @return string */ protected function schemaPath($connection) { if ($this->option('schema-path')) { return $this->option('schema-path'); } if (file_exists($path = database_path('schema/'.$connection->getName().'-schema.dump'))) { return $path; } return database_path('schema/'.$connection->getName().'-schema.sql'); } } Console/Migrations/MigrateMakeCommand.php000064400000010575147536122140014475 0ustar00creator = $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); } /** * Write the migration file to disk. * * @param string $name * @param string $table * @param bool $create * @return void */ protected function writeMigration($name, $table, $create) { $file = $this->creator->create( $name, $this->getMigrationPath(), $table, $create ); $this->components->info(sprintf('Migration [%s] created successfully.', $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(); } /** * Prompt for missing input arguments using the returned questions. * * @return array */ protected function promptForMissingArgumentsUsing() { return [ 'name' => ['What should the migration be named?', 'E.g. create_flights_table'], ]; } } Console/Migrations/RefreshCommand.php000075500000011460147536122140013702 0ustar00confirmToProceed()) { return 1; } // Next we'll gather some of the options so that we can have the right options // to pass to the commands. This includes options such as which database to // use and the path to use for the migration. Then we'll run the command. $database = $this->input->getOption('database'); $path = $this->input->getOption('path'); // If the "step" option is specified it means we only want to rollback a small // number of migrations before migrating again. For example, the user might // only rollback and remigrate the latest four migrations instead of all. $step = $this->input->getOption('step') ?: 0; if ($step > 0) { $this->runRollback($database, $path, $step); } else { $this->runReset($database, $path); } // The refresh command is essentially just a brief aggregate of a few other of // the migration commands and just provides a convenient wrapper to execute // them in succession. We'll also see if we need to re-seed the database. $this->call('migrate', array_filter([ '--database' => $database, '--path' => $path, '--realpath' => $this->input->getOption('realpath'), '--force' => true, ])); if ($this->laravel->bound(Dispatcher::class)) { $this->laravel[Dispatcher::class]->dispatch( new DatabaseRefreshed($database, $this->needsSeeding()) ); } if ($this->needsSeeding()) { $this->runSeeder($database); } return 0; } /** * Run the rollback command. * * @param string $database * @param string $path * @param int $step * @return void */ protected function runRollback($database, $path, $step) { $this->call('migrate:rollback', array_filter([ '--database' => $database, '--path' => $path, '--realpath' => $this->input->getOption('realpath'), '--step' => $step, '--force' => true, ])); } /** * Run the reset command. * * @param string $database * @param string $path * @return void */ protected function runReset($database, $path) { $this->call('migrate:reset', array_filter([ '--database' => $database, '--path' => $path, '--realpath' => $this->input->getOption('realpath'), '--force' => true, ])); } /** * Determine if the developer has requested database seeding. * * @return bool */ protected function needsSeeding() { return $this->option('seed') || $this->option('seeder'); } /** * Run the database seeder command. * * @param string $database * @return void */ protected function runSeeder($database) { $this->call('db:seed', array_filter([ '--database' => $database, '--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder', '--force' => true, ])); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], ['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'], ['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'], ['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted & re-run'], ]; } } Console/Migrations/ResetCommand.php000075500000004764147536122140013377 0ustar00migrator = $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->components->warn('Migration table not found.'); } $this->migrator->setOutput($this->output)->reset( $this->getMigrationPaths(), $this->option('pretend') ); }); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], ['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'], ['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'], ['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'], ]; } } Console/Migrations/RollbackCommand.php000075500000005011147536122140014030 0ustar00migrator = $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'), 'batch' => (int) $this->option('batch'), ] ); }); 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'], ['batch', null, InputOption::VALUE_REQUIRED, 'The batch of migrations (identified by their batch number) to be reverted'], ]; } } Console/Migrations/StatusCommand.php000064400000010047147536122140013564 0ustar00migrator = $migrator; } /** * Execute the console command. * * @return int|null */ public function handle() { return $this->migrator->usingConnection($this->option('database'), function () { if (! $this->migrator->repositoryExists()) { $this->components->error('Migration table not found.'); return 1; } $ran = $this->migrator->getRepository()->getRan(); $batches = $this->migrator->getRepository()->getMigrationBatches(); $migrations = $this->getStatusFor($ran, $batches) ->when($this->option('pending'), fn ($collection) => $collection->filter(function ($migration) { return str($migration[1])->contains('Pending'); })); if (count($migrations) > 0) { $this->newLine(); $this->components->twoColumnDetail('Migration name', 'Batch / Status'); $migrations ->each( fn ($migration) => $this->components->twoColumnDetail($migration[0], $migration[1]) ); $this->newLine(); } elseif ($this->option('pending')) { $this->components->info('No pending migrations'); } else { $this->components->info('No migrations found'); } }); } /** * Get the status for the given run migrations. * * @param array $ran * @param array $batches * @return \Illuminate\Support\Collection */ protected function getStatusFor(array $ran, array $batches) { return Collection::make($this->getAllMigrationFiles()) ->map(function ($migration) use ($ran, $batches) { $migrationName = $this->migrator->getMigrationName($migration); $status = in_array($migrationName, $ran) ? 'Ran' : 'Pending'; if (in_array($migrationName, $ran)) { $status = '['.$batches[$migrationName].'] '.$status; } return [$migrationName, $status]; }); } /** * Get an array of all of the migration files. * * @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'], ['pending', null, InputOption::VALUE_NONE, 'Only list pending migrations'], ['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'], ]; } } Console/Migrations/TableGuesser.php000064400000001613147536122140013366 0ustar00connection = $connection; $this->events = $events; } /** * Execute the console command. * * @return void */ public function handle() { $databases = $this->parseDatabases($this->option('databases')); $this->displayConnections($databases); if ($this->option('max')) { $this->dispatchEvents($databases); } } /** * Parse the database into an array of the connections. * * @param string $databases * @return \Illuminate\Support\Collection */ protected function parseDatabases($databases) { return collect(explode(',', $databases))->map(function ($database) { if (! $database) { $database = $this->laravel['config']['database.default']; } $maxConnections = $this->option('max'); return [ 'database' => $database, 'connections' => $connections = $this->getConnectionCount($this->connection->connection($database)), 'status' => $maxConnections && $connections >= $maxConnections ? 'ALERT' : 'OK', ]; }); } /** * Display the databases and their connection counts in the console. * * @param \Illuminate\Support\Collection $databases * @return void */ protected function displayConnections($databases) { $this->newLine(); $this->components->twoColumnDetail('Database name', 'Connections'); $databases->each(function ($database) { $status = '['.$database['connections'].'] '.$database['status']; $this->components->twoColumnDetail($database['database'], $status); }); $this->newLine(); } /** * Dispatch the database monitoring events. * * @param \Illuminate\Support\Collection $databases * @return void */ protected function dispatchEvents($databases) { $databases->each(function ($database) { if ($database['status'] === 'OK') { return; } $this->events->dispatch( new DatabaseBusy( $database['database'], $database['connections'] ) ); }); } } Console/PruneCommand.php000064400000013045147536122140011257 0ustar00models(); if ($models->isEmpty()) { $this->components->info('No prunable models found.'); return; } if ($this->option('pretend')) { $models->each(function ($model) { $this->pretendToPrune($model); }); return; } $pruning = []; $events->listen(ModelsPruned::class, function ($event) use (&$pruning) { if (! in_array($event->model, $pruning)) { $pruning[] = $event->model; $this->newLine(); $this->components->info(sprintf('Pruning [%s] records.', $event->model)); } $this->components->twoColumnDetail($event->model, "{$event->count} records"); }); $events->dispatch(new ModelPruningStarting($models->all())); $models->each(function ($model) { $this->pruneModel($model); }); $events->dispatch(new ModelPruningFinished($models->all())); $events->forget(ModelsPruned::class); } /** * Prune the given model. * * @param string $model * @return void */ protected function pruneModel(string $model) { $instance = new $model; $chunkSize = property_exists($instance, 'prunableChunkSize') ? $instance->prunableChunkSize : $this->option('chunk'); $total = $this->isPrunable($model) ? $instance->pruneAll($chunkSize) : 0; if ($total == 0) { $this->components->info("No prunable [$model] records found."); } } /** * Determine the models that should be pruned. * * @return \Illuminate\Support\Collection */ protected function models() { if (! empty($models = $this->option('model'))) { return collect($models)->filter(function ($model) { return class_exists($model); })->values(); } $except = $this->option('except'); if (! empty($models) && ! empty($except)) { throw new InvalidArgumentException('The --models and --except options cannot be combined.'); } return collect((new Finder)->in($this->getDefaultPath())->files()->name('*.php')) ->map(function ($model) { $namespace = $this->laravel->getNamespace(); return $namespace.str_replace( ['/', '.php'], ['\\', ''], Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR) ); })->when(! empty($except), function ($models) use ($except) { return $models->reject(function ($model) use ($except) { return in_array($model, $except); }); })->filter(function ($model) { return class_exists($model); })->filter(function ($model) { return $this->isPrunable($model); })->values(); } /** * Get the default path where models are located. * * @return string|string[] */ protected function getDefaultPath() { return app_path('Models'); } /** * Determine if the given model class is prunable. * * @param string $model * @return bool */ protected function isPrunable($model) { $uses = class_uses_recursive($model); return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses); } /** * Display how many models will be pruned. * * @param string $model * @return void */ protected function pretendToPrune($model) { $instance = new $model; $count = $instance->prunable() ->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($instance))), function ($query) { $query->withTrashed(); })->count(); if ($count === 0) { $this->components->info("No prunable [$model] records found."); } else { $this->components->info("{$count} [{$model}] records will be pruned."); } } } Console/Seeds/SeedCommand.php000064400000006647147536122140012123 0ustar00resolver = $resolver; } /** * Execute the console command. * * @return int */ public function handle() { if (! $this->confirmToProceed()) { return 1; } $this->components->info('Seeding database.'); $previousConnection = $this->resolver->getDefaultConnection(); $this->resolver->setDefaultConnection($this->getDatabase()); Model::unguarded(function () { $this->getSeeder()->__invoke(); }); if ($previousConnection) { $this->resolver->setDefaultConnection($previousConnection); } return 0; } /** * Get a seeder instance from the container. * * @return \Illuminate\Database\Seeder */ protected function getSeeder() { $class = $this->input->getArgument('class') ?? $this->input->getOption('class'); if (! str_contains($class, '\\')) { $class = 'Database\\Seeders\\'.$class; } if ($class === 'Database\\Seeders\\DatabaseSeeder' && ! class_exists($class)) { $class = 'DatabaseSeeder'; } return $this->laravel->make($class) ->setContainer($this->laravel) ->setCommand($this); } /** * Get the name of the database connection to use. * * @return string */ protected function getDatabase() { $database = $this->input->getOption('database'); return $database ?: $this->laravel['config']['database.default']; } /** * Get the console command arguments. * * @return array */ protected function getArguments() { return [ ['class', InputArgument::OPTIONAL, 'The class name of the root seeder', null], ]; } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\\Seeders\\DatabaseSeeder'], ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'], ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], ]; } } Console/Seeds/SeederMakeCommand.php000064400000003642147536122140013240 0ustar00resolveStubPath('/stubs/seeder.stub'); } /** * Resolve the fully-qualified path to the stub. * * @param string $stub * @return string */ protected function resolveStubPath($stub) { return is_file($customPath = $this->laravel->basePath(trim($stub, '/'))) ? $customPath : __DIR__.$stub; } /** * Get the destination class path. * * @param string $name * @return string */ protected function getPath($name) { $name = str_replace('\\', '/', Str::replaceFirst($this->rootNamespace(), '', $name)); if (is_dir($this->laravel->databasePath().'/seeds')) { return $this->laravel->databasePath().'/seeds/'.$name.'.php'; } return $this->laravel->databasePath().'/seeders/'.$name.'.php'; } /** * Get the root namespace for the class. * * @return string */ protected function rootNamespace() { return 'Database\Seeders\\'; } } Console/Seeds/WithoutModelEvents.php000064400000000626147536122140013544 0ustar00 Model::withoutEvents($callback); } } Console/Seeds/stubs/seeder.stub000064400000000411147536122140012520 0ustar00 Note: This can be slow on large databases }; {--views : Show the database views Note: This can be slow on large databases }'; /** * The console command description. * * @var string */ protected $description = 'Display information about the given database'; /** * Execute the console command. * * @param \Illuminate\Database\ConnectionResolverInterface $connections * @return int */ public function handle(ConnectionResolverInterface $connections) { if (! $this->ensureDependenciesExist()) { return 1; } $connection = $connections->connection($database = $this->input->getOption('database')); $doctrineConnection = $connection->getDoctrineConnection(); $schema = $connection->getDoctrineSchemaManager(); $this->registerTypeMappings($doctrineConnection->getDatabasePlatform()); $data = [ 'platform' => [ 'config' => $this->getConfigFromDatabase($database), 'name' => $this->getPlatformName($doctrineConnection->getDatabasePlatform(), $database), 'open_connections' => $this->getConnectionCount($connection), ], 'tables' => $this->tables($connection, $schema), ]; if ($this->option('views')) { $data['views'] = $this->collectViews($connection, $schema); } $this->display($data); return 0; } /** * Get information regarding the tables within the database. * * @param \Illuminate\Database\ConnectionInterface $connection * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema * @return \Illuminate\Support\Collection */ protected function tables(ConnectionInterface $connection, AbstractSchemaManager $schema) { return collect($schema->listTables())->map(fn (Table $table, $index) => [ 'table' => $table->getName(), 'size' => $this->getTableSize($connection, $table->getName()), 'rows' => $this->option('counts') ? $connection->table($table->getName())->count() : null, 'engine' => rescue(fn () => $table->getOption('engine'), null, false), 'comment' => $table->getComment(), ]); } /** * Get information regarding the views within the database. * * @param \Illuminate\Database\ConnectionInterface $connection * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema * @return \Illuminate\Support\Collection */ protected function collectViews(ConnectionInterface $connection, AbstractSchemaManager $schema) { return collect($schema->listViews()) ->reject(fn (View $view) => str($view->getName()) ->startsWith(['pg_catalog', 'information_schema', 'spt_'])) ->map(fn (View $view) => [ 'view' => $view->getName(), 'rows' => $connection->table($view->getName())->count(), ]); } /** * Render the database information. * * @param array $data * @return void */ protected function display(array $data) { $this->option('json') ? $this->displayJson($data) : $this->displayForCli($data); } /** * Render the database information as JSON. * * @param array $data * @return void */ protected function displayJson(array $data) { $this->output->writeln(json_encode($data)); } /** * Render the database information formatted for the CLI. * * @param array $data * @return void */ protected function displayForCli(array $data) { $platform = $data['platform']; $tables = $data['tables']; $views = $data['views'] ?? null; $this->newLine(); $this->components->twoColumnDetail(''.$platform['name'].''); $this->components->twoColumnDetail('Database', Arr::get($platform['config'], 'database')); $this->components->twoColumnDetail('Host', Arr::get($platform['config'], 'host')); $this->components->twoColumnDetail('Port', Arr::get($platform['config'], 'port')); $this->components->twoColumnDetail('Username', Arr::get($platform['config'], 'username')); $this->components->twoColumnDetail('URL', Arr::get($platform['config'], 'url')); $this->components->twoColumnDetail('Open Connections', $platform['open_connections']); $this->components->twoColumnDetail('Tables', $tables->count()); if ($tableSizeSum = $tables->sum('size')) { $this->components->twoColumnDetail('Total Size', number_format($tableSizeSum / 1024 / 1024, 2).'MiB'); } $this->newLine(); if ($tables->isNotEmpty()) { $this->components->twoColumnDetail('Table', 'Size (MiB)'.($this->option('counts') ? ' / Rows' : '')); $tables->each(function ($table) { if ($tableSize = $table['size']) { $tableSize = number_format($tableSize / 1024 / 1024, 2); } $this->components->twoColumnDetail( $table['table'].($this->output->isVerbose() ? ' '.$table['engine'].'' : null), ($tableSize ? $tableSize : '—').($this->option('counts') ? ' / '.number_format($table['rows']).'' : '') ); if ($this->output->isVerbose()) { if ($table['comment']) { $this->components->bulletList([ $table['comment'], ]); } } }); $this->newLine(); } if ($views && $views->isNotEmpty()) { $this->components->twoColumnDetail('View', 'Rows'); $views->each(fn ($view) => $this->components->twoColumnDetail($view['view'], number_format($view['rows']))); $this->newLine(); } } } Console/ShowModelCommand.php000064400000041460147536122140012071 0ustar00ensureDependenciesExist()) { return 1; } $class = $this->qualifyModel($this->argument('model')); try { $model = $this->laravel->make($class); $class = get_class($model); } catch (BindingResolutionException $e) { return $this->components->error($e->getMessage()); } if ($this->option('database')) { $model->setConnection($this->option('database')); } $this->display( $class, $model->getConnection()->getName(), $model->getConnection()->getTablePrefix().$model->getTable(), $this->getPolicy($model), $this->getAttributes($model), $this->getRelations($model), $this->getObservers($model), ); } /** * Get the first policy associated with this model. * * @param \Illuminate\Database\Eloquent\Model $model * @return string */ protected function getPolicy($model) { $policy = Gate::getPolicyFor($model::class); return $policy ? $policy::class : null; } /** * Get the column attributes for the given model. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Support\Collection */ protected function getAttributes($model) { $connection = $model->getConnection(); $schema = $connection->getDoctrineSchemaManager(); $this->registerTypeMappings($connection->getDoctrineConnection()->getDatabasePlatform()); $table = $model->getConnection()->getTablePrefix().$model->getTable(); $columns = $schema->listTableColumns($table); $indexes = $schema->listTableIndexes($table); return collect($columns) ->values() ->map(fn (Column $column) => [ 'name' => $column->getName(), 'type' => $this->getColumnType($column), 'increments' => $column->getAutoincrement(), 'nullable' => ! $column->getNotnull(), 'default' => $this->getColumnDefault($column, $model), 'unique' => $this->columnIsUnique($column->getName(), $indexes), 'fillable' => $model->isFillable($column->getName()), 'hidden' => $this->attributeIsHidden($column->getName(), $model), 'appended' => null, 'cast' => $this->getCastType($column->getName(), $model), ]) ->merge($this->getVirtualAttributes($model, $columns)); } /** * Get the virtual (non-column) attributes for the given model. * * @param \Illuminate\Database\Eloquent\Model $model * @param \Doctrine\DBAL\Schema\Column[] $columns * @return \Illuminate\Support\Collection */ protected function getVirtualAttributes($model, $columns) { $class = new ReflectionClass($model); return collect($class->getMethods()) ->reject( fn (ReflectionMethod $method) => $method->isStatic() || $method->isAbstract() || $method->getDeclaringClass()->getName() !== get_class($model) ) ->mapWithKeys(function (ReflectionMethod $method) use ($model) { if (preg_match('/^get(.+)Attribute$/', $method->getName(), $matches) === 1) { return [Str::snake($matches[1]) => 'accessor']; } elseif ($model->hasAttributeMutator($method->getName())) { return [Str::snake($method->getName()) => 'attribute']; } else { return []; } }) ->reject(fn ($cast, $name) => collect($columns)->has($name)) ->map(fn ($cast, $name) => [ 'name' => $name, 'type' => null, 'increments' => false, 'nullable' => null, 'default' => null, 'unique' => null, 'fillable' => $model->isFillable($name), 'hidden' => $this->attributeIsHidden($name, $model), 'appended' => $model->hasAppended($name), 'cast' => $cast, ]) ->values(); } /** * Get the relations from the given model. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Support\Collection */ protected function getRelations($model) { return collect(get_class_methods($model)) ->map(fn ($method) => new ReflectionMethod($model, $method)) ->reject( fn (ReflectionMethod $method) => $method->isStatic() || $method->isAbstract() || $method->getDeclaringClass()->getName() !== get_class($model) ) ->filter(function (ReflectionMethod $method) { $file = new SplFileObject($method->getFileName()); $file->seek($method->getStartLine() - 1); $code = ''; while ($file->key() < $method->getEndLine()) { $code .= trim($file->current()); $file->next(); } return collect($this->relationMethods) ->contains(fn ($relationMethod) => str_contains($code, '$this->'.$relationMethod.'(')); }) ->map(function (ReflectionMethod $method) use ($model) { $relation = $method->invoke($model); if (! $relation instanceof Relation) { return null; } return [ 'name' => $method->getName(), 'type' => Str::afterLast(get_class($relation), '\\'), 'related' => get_class($relation->getRelated()), ]; }) ->filter() ->values(); } /** * Get the Observers watching this model. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Support\Collection */ protected function getObservers($model) { $listeners = $this->getLaravel()->make('events')->getRawListeners(); // Get the Eloquent observers for this model... $listeners = array_filter($listeners, function ($v, $key) use ($model) { return Str::startsWith($key, 'eloquent.') && Str::endsWith($key, $model::class); }, ARRAY_FILTER_USE_BOTH); // Format listeners Eloquent verb => Observer methods... $extractVerb = function ($key) { preg_match('/eloquent.([a-zA-Z]+)\: /', $key, $matches); return $matches[1] ?? '?'; }; $formatted = []; foreach ($listeners as $key => $observerMethods) { $formatted[] = [ 'event' => $extractVerb($key), 'observer' => array_map(fn ($obs) => is_string($obs) ? $obs : 'Closure', $observerMethods), ]; } return collect($formatted); } /** * Render the model information. * * @param string $class * @param string $database * @param string $table * @param string $policy * @param \Illuminate\Support\Collection $attributes * @param \Illuminate\Support\Collection $relations * @param \Illuminate\Support\Collection $observers * @return void */ protected function display($class, $database, $table, $policy, $attributes, $relations, $observers) { $this->option('json') ? $this->displayJson($class, $database, $table, $policy, $attributes, $relations, $observers) : $this->displayCli($class, $database, $table, $policy, $attributes, $relations, $observers); } /** * Render the model information as JSON. * * @param string $class * @param string $database * @param string $table * @param string $policy * @param \Illuminate\Support\Collection $attributes * @param \Illuminate\Support\Collection $relations * @param \Illuminate\Support\Collection $observers * @return void */ protected function displayJson($class, $database, $table, $policy, $attributes, $relations, $observers) { $this->output->writeln( collect([ 'class' => $class, 'database' => $database, 'table' => $table, 'policy' => $policy, 'attributes' => $attributes, 'relations' => $relations, 'observers' => $observers, ])->toJson() ); } /** * Render the model information for the CLI. * * @param string $class * @param string $database * @param string $table * @param string $policy * @param \Illuminate\Support\Collection $attributes * @param \Illuminate\Support\Collection $relations * @param \Illuminate\Support\Collection $observers * @return void */ protected function displayCli($class, $database, $table, $policy, $attributes, $relations, $observers) { $this->newLine(); $this->components->twoColumnDetail(''.$class.''); $this->components->twoColumnDetail('Database', $database); $this->components->twoColumnDetail('Table', $table); if ($policy) { $this->components->twoColumnDetail('Policy', $policy); } $this->newLine(); $this->components->twoColumnDetail( 'Attributes', 'type / cast', ); foreach ($attributes as $attribute) { $first = trim(sprintf( '%s %s', $attribute['name'], collect(['increments', 'unique', 'nullable', 'fillable', 'hidden', 'appended']) ->filter(fn ($property) => $attribute[$property]) ->map(fn ($property) => sprintf('%s', $property)) ->implode(', ') )); $second = collect([ $attribute['type'], $attribute['cast'] ? ''.$attribute['cast'].'' : null, ])->filter()->implode(' / '); $this->components->twoColumnDetail($first, $second); if ($attribute['default'] !== null) { $this->components->bulletList( [sprintf('default: %s', $attribute['default'])], OutputInterface::VERBOSITY_VERBOSE ); } } $this->newLine(); $this->components->twoColumnDetail('Relations'); foreach ($relations as $relation) { $this->components->twoColumnDetail( sprintf('%s %s', $relation['name'], $relation['type']), $relation['related'] ); } $this->newLine(); $this->components->twoColumnDetail('Observers'); if ($observers->count()) { foreach ($observers as $observer) { $this->components->twoColumnDetail( sprintf('%s', $observer['event']), implode(', ', $observer['observer']) ); } } $this->newLine(); } /** * Get the cast type for the given column. * * @param string $column * @param \Illuminate\Database\Eloquent\Model $model * @return string|null */ protected function getCastType($column, $model) { if ($model->hasGetMutator($column) || $model->hasSetMutator($column)) { return 'accessor'; } if ($model->hasAttributeMutator($column)) { return 'attribute'; } return $this->getCastsWithDates($model)->get($column) ?? null; } /** * Get the model casts, including any date casts. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Support\Collection */ protected function getCastsWithDates($model) { return collect($model->getDates()) ->filter() ->flip() ->map(fn () => 'datetime') ->merge($model->getCasts()); } /** * Get the type of the given column. * * @param \Doctrine\DBAL\Schema\Column $column * @return string */ protected function getColumnType($column) { $name = $column->getType()->getName(); $unsigned = $column->getUnsigned() ? ' unsigned' : ''; $details = match (get_class($column->getType())) { DecimalType::class => $column->getPrecision().','.$column->getScale(), default => $column->getLength(), }; if ($details) { return sprintf('%s(%s)%s', $name, $details, $unsigned); } return sprintf('%s%s', $name, $unsigned); } /** * Get the default value for the given column. * * @param \Doctrine\DBAL\Schema\Column $column * @param \Illuminate\Database\Eloquent\Model $model * @return mixed|null */ protected function getColumnDefault($column, $model) { $attributeDefault = $model->getAttributes()[$column->getName()] ?? null; return match (true) { $attributeDefault instanceof BackedEnum => $attributeDefault->value, $attributeDefault instanceof UnitEnum => $attributeDefault->name, default => $attributeDefault ?? $column->getDefault(), }; } /** * Determine if the given attribute is hidden. * * @param string $attribute * @param \Illuminate\Database\Eloquent\Model $model * @return bool */ protected function attributeIsHidden($attribute, $model) { if (count($model->getHidden()) > 0) { return in_array($attribute, $model->getHidden()); } if (count($model->getVisible()) > 0) { return ! in_array($attribute, $model->getVisible()); } return false; } /** * Determine if the given attribute is unique. * * @param string $column * @param \Doctrine\DBAL\Schema\Index[] $indexes * @return bool */ protected function columnIsUnique($column, $indexes) { return collect($indexes) ->filter(fn (Index $index) => count($index->getColumns()) === 1 && $index->getColumns()[0] === $column) ->contains(fn (Index $index) => $index->isUnique()); } /** * Qualify the given model class base name. * * @param string $model * @return string * * @see \Illuminate\Console\GeneratorCommand */ protected function qualifyModel(string $model) { if (str_contains($model, '\\') && class_exists($model)) { return $model; } $model = ltrim($model, '\\/'); $model = str_replace('/', '\\', $model); $rootNamespace = $this->laravel->getNamespace(); if (Str::startsWith($model, $rootNamespace)) { return $model; } return is_dir(app_path('Models')) ? $rootNamespace.'Models\\'.$model : $rootNamespace.$model; } } Console/TableCommand.php000064400000017620147536122140011220 0ustar00ensureDependenciesExist()) { return 1; } $connection = $connections->connection($this->input->getOption('database')); $schema = $connection->getDoctrineSchemaManager(); $this->registerTypeMappings($connection->getDoctrineConnection()->getDatabasePlatform()); $table = $this->argument('table') ?: select( 'Which table would you like to inspect?', collect($schema->listTables())->flatMap(fn (Table $table) => [$table->getName()])->toArray() ); if (! $schema->tablesExist([$table])) { return $this->components->warn("Table [{$table}] doesn't exist."); } $table = $schema->introspectTable($table); $columns = $this->columns($table); $indexes = $this->indexes($table); $foreignKeys = $this->foreignKeys($table); $data = [ 'table' => [ 'name' => $table->getName(), 'columns' => $columns->count(), 'size' => $this->getTableSize($connection, $table->getName()), ], 'columns' => $columns, 'indexes' => $indexes, 'foreign_keys' => $foreignKeys, ]; $this->display($data); return 0; } /** * Get the information regarding the table's columns. * * @param \Doctrine\DBAL\Schema\Table $table * @return \Illuminate\Support\Collection */ protected function columns(Table $table) { return collect($table->getColumns())->map(fn (Column $column) => [ 'column' => $column->getName(), 'attributes' => $this->getAttributesForColumn($column), 'default' => $column->getDefault(), 'type' => $column->getType()->getName(), ]); } /** * Get the attributes for a table column. * * @param \Doctrine\DBAL\Schema\Column $column * @return \Illuminate\Support\Collection */ protected function getAttributesForColumn(Column $column) { return collect([ $column->getAutoincrement() ? 'autoincrement' : null, 'type' => $column->getType()->getName(), $column->getUnsigned() ? 'unsigned' : null, ! $column->getNotNull() ? 'nullable' : null, ])->filter(); } /** * Get the information regarding the table's indexes. * * @param \Doctrine\DBAL\Schema\Table $table * @return \Illuminate\Support\Collection */ protected function indexes(Table $table) { return collect($table->getIndexes())->map(fn (Index $index) => [ 'name' => $index->getName(), 'columns' => collect($index->getColumns()), 'attributes' => $this->getAttributesForIndex($index), ]); } /** * Get the attributes for a table index. * * @param \Doctrine\DBAL\Schema\Index $index * @return \Illuminate\Support\Collection */ protected function getAttributesForIndex(Index $index) { return collect([ 'compound' => count($index->getColumns()) > 1, 'unique' => $index->isUnique(), 'primary' => $index->isPrimary(), ])->filter()->keys()->map(fn ($attribute) => Str::lower($attribute)); } /** * Get the information regarding the table's foreign keys. * * @param \Doctrine\DBAL\Schema\Table $table * @return \Illuminate\Support\Collection */ protected function foreignKeys(Table $table) { return collect($table->getForeignKeys())->map(fn (ForeignKeyConstraint $foreignKey) => [ 'name' => $foreignKey->getName(), 'local_table' => $table->getName(), 'local_columns' => collect($foreignKey->getLocalColumns()), 'foreign_table' => $foreignKey->getForeignTableName(), 'foreign_columns' => collect($foreignKey->getForeignColumns()), 'on_update' => Str::lower(rescue(fn () => $foreignKey->getOption('onUpdate'), 'N/A')), 'on_delete' => Str::lower(rescue(fn () => $foreignKey->getOption('onDelete'), 'N/A')), ]); } /** * Render the table information. * * @param array $data * @return void */ protected function display(array $data) { $this->option('json') ? $this->displayJson($data) : $this->displayForCli($data); } /** * Render the table information as JSON. * * @param array $data * @return void */ protected function displayJson(array $data) { $this->output->writeln(json_encode($data)); } /** * Render the table information formatted for the CLI. * * @param array $data * @return void */ protected function displayForCli(array $data) { [$table, $columns, $indexes, $foreignKeys] = [ $data['table'], $data['columns'], $data['indexes'], $data['foreign_keys'], ]; $this->newLine(); $this->components->twoColumnDetail(''.$table['name'].''); $this->components->twoColumnDetail('Columns', $table['columns']); if ($size = $table['size']) { $this->components->twoColumnDetail('Size', number_format($size / 1024 / 1024, 2).'MiB'); } $this->newLine(); if ($columns->isNotEmpty()) { $this->components->twoColumnDetail('Column', 'Type'); $columns->each(function ($column) { $this->components->twoColumnDetail( $column['column'].' '.$column['attributes']->implode(', ').'', ($column['default'] ? ''.$column['default'].' ' : '').''.$column['type'].'' ); }); $this->newLine(); } if ($indexes->isNotEmpty()) { $this->components->twoColumnDetail('Index'); $indexes->each(function ($index) { $this->components->twoColumnDetail( $index['name'].' '.$index['columns']->implode(', ').'', $index['attributes']->implode(', ') ); }); $this->newLine(); } if ($foreignKeys->isNotEmpty()) { $this->components->twoColumnDetail('Foreign Key', 'On Update / On Delete'); $foreignKeys->each(function ($foreignKey) { $this->components->twoColumnDetail( $foreignKey['name'].' '.$foreignKey['local_columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'', $foreignKey['on_update'].' / '.$foreignKey['on_delete'], ); }); $this->newLine(); } } } Console/WipeCommand.php000064400000005323147536122140011072 0ustar00confirmToProceed()) { return 1; } $database = $this->input->getOption('database'); if ($this->option('drop-views')) { $this->dropAllViews($database); $this->components->info('Dropped all views successfully.'); } $this->dropAllTables($database); $this->components->info('Dropped all tables successfully.'); if ($this->option('drop-types')) { $this->dropAllTypes($database); $this->components->info('Dropped all types successfully.'); } return 0; } /** * Drop all of the database tables. * * @param string $database * @return void */ protected function dropAllTables($database) { $this->laravel['db']->connection($database) ->getSchemaBuilder() ->dropAllTables(); } /** * Drop all of the database views. * * @param string $database * @return void */ protected function dropAllViews($database) { $this->laravel['db']->connection($database) ->getSchemaBuilder() ->dropAllViews(); } /** * Drop all of the database types. * * @param string $database * @return void */ protected function dropAllTypes($database) { $this->laravel['db']->connection($database) ->getSchemaBuilder() ->dropAllTypes(); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'], ['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'], ['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'], ['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'], ]; } } DBAL/TimestampType.php000064400000006070147536122140010574 0ustar00 $this->getMySqlPlatformSQLDeclaration($column), PostgreSQLPlatform::class, PostgreSQL94Platform::class, PostgreSQL100Platform::class => $this->getPostgresPlatformSQLDeclaration($column), SQLServerPlatform::class, SQLServer2012Platform::class => $this->getSqlServerPlatformSQLDeclaration($column), SqlitePlatform::class => 'DATETIME', default => throw new DBALException('Invalid platform: '.substr(strrchr(get_class($platform), '\\'), 1)), }; } /** * Get the SQL declaration for MySQL. * * @param array $column * @return string */ protected function getMySqlPlatformSQLDeclaration(array $column): string { $columnType = 'TIMESTAMP'; if ($column['precision']) { $columnType = 'TIMESTAMP('.min((int) $column['precision'], 6).')'; } $notNull = $column['notnull'] ?? false; if (! $notNull) { return $columnType.' NULL'; } return $columnType; } /** * Get the SQL declaration for PostgreSQL. * * @param array $column * @return string */ protected function getPostgresPlatformSQLDeclaration(array $column): string { return 'TIMESTAMP('.min((int) $column['precision'], 6).')'; } /** * Get the SQL declaration for SQL Server. * * @param array $column * @return string */ protected function getSqlServerPlatformSQLDeclaration(array $column): string { return $column['precision'] ?? false ? 'DATETIME2('.min((int) $column['precision'], 7).')' : 'DATETIME'; } /** * {@inheritdoc} * * @return string */ public function getName() { return 'timestamp'; } } DatabaseManager.php000075500000031435147536122140010272 0ustar00 */ protected $connections = []; /** * The custom connection resolvers. * * @var array */ protected $extensions = []; /** * The callback to be executed to reconnect to a database. * * @var callable */ protected $reconnector; /** * The custom Doctrine column types. * * @var array */ protected $doctrineTypes = []; /** * Create a new database manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Database\Connectors\ConnectionFactory $factory * @return void */ public function __construct($app, ConnectionFactory $factory) { $this->app = $app; $this->factory = $factory; $this->reconnector = function ($connection) { $this->reconnect($connection->getNameWithReadWriteType()); }; } /** * Get a database connection instance. * * @param string|null $name * @return \Illuminate\Database\Connection */ public function connection($name = null) { [$database, $type] = $this->parseConnectionName($name); $name = $name ?: $database; // If we haven't created this connection, we'll create it based on the config // provided in the application. Once we've created the connections we will // set the "fetch mode" for PDO which determines the query return types. if (! isset($this->connections[$name])) { $this->connections[$name] = $this->configure( $this->makeConnection($database), $type ); if ($this->app->bound('events')) { $this->app['events']->dispatch( new ConnectionEstablished($this->connections[$name]) ); } } return $this->connections[$name]; } /** * Parse the connection into an array of the name and read / write type. * * @param string $name * @return array */ protected function parseConnectionName($name) { $name = $name ?: $this->getDefaultConnection(); return Str::endsWith($name, ['::read', '::write']) ? explode('::', $name, 2) : [$name, null]; } /** * Make the database connection instance. * * @param string $name * @return \Illuminate\Database\Connection */ protected function makeConnection($name) { $config = $this->configuration($name); // First we will check by the connection name to see if an extension has been // registered specifically for that connection. If it has we will call the // Closure and pass it the config allowing it to resolve the connection. if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); } // Next we will check to see if an extension has been registered for a driver // and will call the Closure if so, which allows us to have a more generic // resolver for the drivers themselves which applies to all connections. if (isset($this->extensions[$driver = $config['driver']])) { return call_user_func($this->extensions[$driver], $config, $name); } return $this->factory->make($config, $name); } /** * Get the configuration for a connection. * * @param string $name * @return array * * @throws \InvalidArgumentException */ protected function configuration($name) { $name = $name ?: $this->getDefaultConnection(); // To get the database connection configuration, we will just pull each of the // connection configurations and get the configurations for the given name. // If the configuration doesn't exist, we'll throw an exception and bail. $connections = $this->app['config']['database.connections']; if (is_null($config = Arr::get($connections, $name))) { throw new InvalidArgumentException("Database connection [{$name}] not configured."); } return (new ConfigurationUrlParser) ->parseConfiguration($config); } /** * Prepare the database connection instance. * * @param \Illuminate\Database\Connection $connection * @param string $type * @return \Illuminate\Database\Connection */ protected function configure(Connection $connection, $type) { $connection = $this->setPdoForType($connection, $type)->setReadWriteType($type); // First we'll set the fetch mode and a few other dependencies of the database // connection. This method basically just configures and prepares it to get // used by the application. Once we're finished we'll return it back out. if ($this->app->bound('events')) { $connection->setEventDispatcher($this->app['events']); } if ($this->app->bound('db.transactions')) { $connection->setTransactionManager($this->app['db.transactions']); } // Here we'll set a reconnector callback. This reconnector can be any callable // so we will set a Closure to reconnect from this manager with the name of // the connection, which will allow us to reconnect from the connections. $connection->setReconnector($this->reconnector); $this->registerConfiguredDoctrineTypes($connection); 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; } /** * Register custom Doctrine types with the connection. * * @param \Illuminate\Database\Connection $connection * @return void */ protected function registerConfiguredDoctrineTypes(Connection $connection): void { foreach ($this->app['config']->get('database.dbal.types', []) as $name => $class) { $this->registerDoctrineType($class, $name, $name); } foreach ($this->doctrineTypes as $name => [$type, $class]) { $connection->registerDoctrineType($class, $name, $type); } } /** * Register a custom Doctrine type. * * @param string $class * @param string $name * @param string $type * @return void * * @throws \Doctrine\DBAL\Exception * @throws \RuntimeException */ public function registerDoctrineType(string $class, string $name, string $type): void { if (! class_exists('Doctrine\DBAL\Connection')) { throw new RuntimeException( 'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).' ); } if (! Type::hasType($name)) { Type::addType($name, $class); } $this->doctrineTypes[$name] = [$type, $class]; } /** * Disconnect from the given database and remove from local cache. * * @param string|null $name * @return void */ public function purge($name = null) { $name = $name ?: $this->getDefaultConnection(); $this->disconnect($name); unset($this->connections[$name]); } /** * Disconnect from the given database. * * @param string|null $name * @return void */ public function disconnect($name = null) { if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) { $this->connections[$name]->disconnect(); } } /** * Reconnect to the given database. * * @param string|null $name * @return \Illuminate\Database\Connection */ public function reconnect($name = null) { $this->disconnect($name = $name ?: $this->getDefaultConnection()); if (! isset($this->connections[$name])) { return $this->connection($name); } return $this->refreshPdoConnections($name); } /** * Set the default database connection for the callback execution. * * @param string $name * @param callable $callback * @return mixed */ public function usingConnection($name, callable $callback) { $previousName = $this->getDefaultConnection(); $this->setDefaultConnection($name); return tap($callback(), function () use ($previousName) { $this->setDefaultConnection($previousName); }); } /** * Refresh the PDO connections on a given connection. * * @param string $name * @return \Illuminate\Database\Connection */ protected function refreshPdoConnections($name) { [$database, $type] = $this->parseConnectionName($name); $fresh = $this->configure( $this->makeConnection($database), $type ); return $this->connections[$name] ->setPdo($fresh->getRawPdo()) ->setReadPdo($fresh->getRawReadPdo()); } /** * Get the default connection name. * * @return string */ public function getDefaultConnection() { return $this->app['config']['database.default']; } /** * Set the default connection name. * * @param string $name * @return void */ public function setDefaultConnection($name) { $this->app['config']['database.default'] = $name; } /** * Get all of the support drivers. * * @return string[] */ public function supportedDrivers() { return ['mysql', 'pgsql', 'sqlite', 'sqlsrv']; } /** * Get all of the drivers that are actually available. * * @return string[] */ 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; } /** * Remove an extension connection resolver. * * @param string $name * @return void */ public function forgetExtension($name) { unset($this->extensions[$name]); } /** * Return all of the created connections. * * @return array */ public function getConnections() { return $this->connections; } /** * Set the database reconnector callback. * * @param callable $reconnector * @return void */ public function setReconnector(callable $reconnector) { $this->reconnector = $reconnector; } /** * Set the application instance used by the manager. * * @param \Illuminate\Contracts\Foundation\Application $app * @return $this */ public function setApplication($app) { $this->app = $app; return $this; } /** * Dynamically pass methods to the default connection. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } return $this->connection()->$method(...$parameters); } } DatabaseServiceProvider.php000075500000006303147536122140012027 0ustar00app['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(); }); $this->app->bind('db.schema', function ($app) { return $app['db']->connection()->getSchemaBuilder(); }); $this->app->singleton('db.transactions', function ($app) { return new DatabaseTransactionsManager; }); } /** * Register the Eloquent factory instance in the container. * * @return void */ protected function registerEloquentFactory() { $this->app->singleton(FakerGenerator::class, function ($app, $parameters) { $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US'); if (! isset(static::$fakers[$locale])) { static::$fakers[$locale] = FakerFactory::create($locale); } static::$fakers[$locale]->unique(true); return static::$fakers[$locale]; }); } /** * Register the queueable entity resolver implementation. * * @return void */ protected function registerQueueableEntityResolver() { $this->app->singleton(EntityResolver::class, function () { return new QueueEntityResolver; }); } } DatabaseTransactionRecord.php000075500000002450147536122140012337 0ustar00connection = $connection; $this->level = $level; } /** * Register a callback to be executed after committing. * * @param callable $callback * @return void */ public function addCallback($callback) { $this->callbacks[] = $callback; } /** * Execute all of the callbacks. * * @return void */ public function executeCallbacks() { foreach ($this->callbacks as $callback) { $callback(); } } /** * Get all of the callbacks. * * @return array */ public function getCallbacks() { return $this->callbacks; } } DatabaseTransactionsManager.php000075500000005236147536122140012663 0ustar00 */ protected $transactions; /** * Create a new database transactions manager instance. * * @return void */ public function __construct() { $this->transactions = collect(); } /** * Start a new database transaction. * * @param string $connection * @param int $level * @return void */ public function begin($connection, $level) { $this->transactions->push( new DatabaseTransactionRecord($connection, $level) ); } /** * Rollback the active database transaction. * * @param string $connection * @param int $level * @return void */ public function rollback($connection, $level) { $this->transactions = $this->transactions->reject( fn ($transaction) => $transaction->connection == $connection && $transaction->level > $level )->values(); } /** * Commit the active database transaction. * * @param string $connection * @return void */ public function commit($connection) { [$forThisConnection, $forOtherConnections] = $this->transactions->partition( fn ($transaction) => $transaction->connection == $connection ); $this->transactions = $forOtherConnections->values(); $forThisConnection->map->executeCallbacks(); } /** * Register a transaction callback. * * @param callable $callback * @return void */ public function addCallback($callback) { if ($current = $this->callbackApplicableTransactions()->last()) { return $current->addCallback($callback); } $callback(); } /** * Get the transactions that are applicable to callbacks. * * @return \Illuminate\Support\Collection */ public function callbackApplicableTransactions() { return $this->transactions; } /** * Determine if after commit callbacks should be executed for the given transaction level. * * @param int $level * @return bool */ public function afterCommitCallbacksShouldBeExecuted($level) { return $level === 0; } /** * Get all the transactions. * * @return \Illuminate\Support\Collection */ public function getTransactions() { return $this->transactions; } } DeadlockException.php000064400000000162147536122140010646 0ustar00getCode() === 40001 || $e->getCode() === '40001')) { return true; } $message = $e->getMessage(); return Str::contains($message, [ 'Deadlock found when trying to get lock', 'deadlock detected', 'The database file is locked', 'database is locked', 'database table is locked', 'A table in the database is locked', 'has been chosen as the deadlock victim', 'Lock wait timeout exceeded; try restarting transaction', 'WSREP detected deadlock/conflict and aborted the transaction. Try restarting the transaction', ]); } } DetectsLostConnections.php000064400000007512147536122140011727 0ustar00getMessage(); 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] [2002] php_network_getaddresses: getaddrinfo for', 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected', 'SQLSTATE[HY000] [2002] Connection timed out', 'SSL: Connection timed out', 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', 'Temporary failure in name resolution', 'SSL: Broken pipe', 'SQLSTATE[08S01]: Communication link failure', 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host', 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host', 'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.', 'SQLSTATE[08006] [7] could not translate host name', 'TCP Provider: Error code 0x274C', 'SQLSTATE[HY000] [2002] No such file or directory', 'SSL: Operation timed out', 'Reason: Server is in script upgrade mode. Only administrator can connect at this time.', 'Unknown $curl_error_code: 77', 'SSL: Handshake timed out', 'SQLSTATE[08006] [7] SSL error: sslv3 alert unexpected message', 'SQLSTATE[08006] [7] unrecognized SSL error code:', 'SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it', 'SQLSTATE[HY000] [2002] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond', 'SQLSTATE[HY000] [2002] Network is unreachable', 'SQLSTATE[HY000] [2002] The requested address is not valid in its context', 'SQLSTATE[HY000] [2002] A socket operation was attempted to an unreachable network', ]); } } Eloquent/BroadcastableModelEventOccurred.php000064400000006322147536122140015261 0ustar00model = $model; $this->event = $event; } /** * The channels the event should broadcast on. * * @return array */ public function broadcastOn() { $channels = empty($this->channels) ? ($this->model->broadcastOn($this->event) ?: []) : $this->channels; return collect($channels)->map(function ($channel) { return $channel instanceof Model ? new PrivateChannel($channel) : $channel; })->all(); } /** * The name the event should broadcast as. * * @return string */ public function broadcastAs() { $default = class_basename($this->model).ucfirst($this->event); return method_exists($this->model, 'broadcastAs') ? ($this->model->broadcastAs($this->event) ?: $default) : $default; } /** * Get the data that should be sent with the broadcasted event. * * @return array|null */ public function broadcastWith() { return method_exists($this->model, 'broadcastWith') ? $this->model->broadcastWith($this->event) : null; } /** * Manually specify the channels the event should broadcast on. * * @param array $channels * @return $this */ public function onChannels(array $channels) { $this->channels = $channels; return $this; } /** * Determine if the event should be broadcast synchronously. * * @return bool */ public function shouldBroadcastNow() { return $this->event === 'deleted' && ! method_exists($this->model, 'bootSoftDeletes'); } /** * Get the event name. * * @return string */ public function event() { return $this->event; } } Eloquent/BroadcastsEvents.php000064400000013354147536122140012336 0ustar00broadcastCreated(); }); static::updated(function ($model) { $model->broadcastUpdated(); }); if (method_exists(static::class, 'bootSoftDeletes')) { static::softDeleted(function ($model) { $model->broadcastTrashed(); }); static::restored(function ($model) { $model->broadcastRestored(); }); } static::deleted(function ($model) { $model->broadcastDeleted(); }); } /** * Broadcast that the model was created. * * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels * @return \Illuminate\Broadcasting\PendingBroadcast */ public function broadcastCreated($channels = null) { return $this->broadcastIfBroadcastChannelsExistForEvent( $this->newBroadcastableModelEvent('created'), 'created', $channels ); } /** * Broadcast that the model was updated. * * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels * @return \Illuminate\Broadcasting\PendingBroadcast */ public function broadcastUpdated($channels = null) { return $this->broadcastIfBroadcastChannelsExistForEvent( $this->newBroadcastableModelEvent('updated'), 'updated', $channels ); } /** * Broadcast that the model was trashed. * * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels * @return \Illuminate\Broadcasting\PendingBroadcast */ public function broadcastTrashed($channels = null) { return $this->broadcastIfBroadcastChannelsExistForEvent( $this->newBroadcastableModelEvent('trashed'), 'trashed', $channels ); } /** * Broadcast that the model was restored. * * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels * @return \Illuminate\Broadcasting\PendingBroadcast */ public function broadcastRestored($channels = null) { return $this->broadcastIfBroadcastChannelsExistForEvent( $this->newBroadcastableModelEvent('restored'), 'restored', $channels ); } /** * Broadcast that the model was deleted. * * @param \Illuminate\Broadcasting\Channel|\Illuminate\Contracts\Broadcasting\HasBroadcastChannel|array|null $channels * @return \Illuminate\Broadcasting\PendingBroadcast */ public function broadcastDeleted($channels = null) { return $this->broadcastIfBroadcastChannelsExistForEvent( $this->newBroadcastableModelEvent('deleted'), 'deleted', $channels ); } /** * Broadcast the given event instance if channels are configured for the model event. * * @param mixed $instance * @param string $event * @param mixed $channels * @return \Illuminate\Broadcasting\PendingBroadcast|null */ protected function broadcastIfBroadcastChannelsExistForEvent($instance, $event, $channels = null) { if (! static::$isBroadcasting) { return; } if (! empty($this->broadcastOn($event)) || ! empty($channels)) { return broadcast($instance->onChannels(Arr::wrap($channels))); } } /** * Create a new broadcastable model event event. * * @param string $event * @return mixed */ public function newBroadcastableModelEvent($event) { return tap($this->newBroadcastableEvent($event), function ($event) { $event->connection = property_exists($this, 'broadcastConnection') ? $this->broadcastConnection : $this->broadcastConnection(); $event->queue = property_exists($this, 'broadcastQueue') ? $this->broadcastQueue : $this->broadcastQueue(); $event->afterCommit = property_exists($this, 'broadcastAfterCommit') ? $this->broadcastAfterCommit : $this->broadcastAfterCommit(); }); } /** * Create a new broadcastable model event for the model. * * @param string $event * @return \Illuminate\Database\Eloquent\BroadcastableModelEventOccurred */ protected function newBroadcastableEvent(string $event) { return new BroadcastableModelEventOccurred($this, $event); } /** * Get the channels that model events should broadcast on. * * @param string $event * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn($event) { return [$this]; } /** * Get the queue connection that should be used to broadcast model events. * * @return string|null */ public function broadcastConnection() { // } /** * Get the queue that should be used to broadcast model events. * * @return string|null */ public function broadcastQueue() { // } /** * Determine if the model event broadcast queued job should be dispatched after all transactions are committed. * * @return bool */ public function broadcastAfterCommit() { return false; } } Eloquent/Builder.php000075500000162712147536122140010460 0ustar00query = $query; } /** * Create and return an un-saved model instance. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model|static */ public function make(array $attributes = []) { return $this->newModelInstance($attributes); } /** * Register a new global scope. * * @param string $identifier * @param \Illuminate\Database\Eloquent\Scope|\Closure $scope * @return $this */ public function withGlobalScope($identifier, $scope) { $this->scopes[$identifier] = $scope; if (method_exists($scope, 'extend')) { $scope->extend($this); } return $this; } /** * Remove a registered global scope. * * @param \Illuminate\Database\Eloquent\Scope|string $scope * @return $this */ public function withoutGlobalScope($scope) { if (! is_string($scope)) { $scope = get_class($scope); } unset($this->scopes[$scope]); $this->removedScopes[] = $scope; return $this; } /** * Remove all or passed registered global scopes. * * @param array|null $scopes * @return $this */ public function withoutGlobalScopes(array $scopes = null) { if (! is_array($scopes)) { $scopes = array_keys($this->scopes); } foreach ($scopes as $scope) { $this->withoutGlobalScope($scope); } return $this; } /** * Get an array of global scopes that were removed from the query. * * @return array */ public function removedScopes() { return $this->removedScopes; } /** * Add a where clause on the primary key to the query. * * @param mixed $id * @return $this */ public function whereKey($id) { if ($id instanceof Model) { $id = $id->getKey(); } if (is_array($id) || $id instanceof Arrayable) { if (in_array($this->model->getKeyType(), ['int', 'integer'])) { $this->query->whereIntegerInRaw($this->model->getQualifiedKeyName(), $id); } else { $this->query->whereIn($this->model->getQualifiedKeyName(), $id); } return $this; } if ($id !== null && $this->model->getKeyType() === 'string') { $id = (string) $id; } return $this->where($this->model->getQualifiedKeyName(), '=', $id); } /** * Add a where clause on the primary key to the query. * * @param mixed $id * @return $this */ public function whereKeyNot($id) { if ($id instanceof Model) { $id = $id->getKey(); } if (is_array($id) || $id instanceof Arrayable) { if (in_array($this->model->getKeyType(), ['int', 'integer'])) { $this->query->whereIntegerNotInRaw($this->model->getQualifiedKeyName(), $id); } else { $this->query->whereNotIn($this->model->getQualifiedKeyName(), $id); } return $this; } if ($id !== null && $this->model->getKeyType() === 'string') { $id = (string) $id; } return $this->where($this->model->getQualifiedKeyName(), '!=', $id); } /** * Add a basic where clause to the query. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return $this */ public function where($column, $operator = null, $value = null, $boolean = 'and') { if ($column instanceof Closure && is_null($operator)) { $column($query = $this->model->newQueryWithoutRelationships()); $this->query->addNestedWhereQuery($query->getQuery(), $boolean); } else { $this->query->where(...func_get_args()); } return $this; } /** * Add a basic where clause to the query, and return the first result. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return \Illuminate\Database\Eloquent\Model|static|null */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { return $this->where(...func_get_args())->first(); } /** * Add an "or where" clause to the query. * * @param \Closure|array|string|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this */ public function orWhere($column, $operator = null, $value = null) { [$value, $operator] = $this->query->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->where($column, $operator, $value, 'or'); } /** * Add a basic "where not" clause to the query. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return $this */ public function whereNot($column, $operator = null, $value = null, $boolean = 'and') { return $this->where($column, $operator, $value, $boolean.' not'); } /** * Add an "or where not" clause to the query. * * @param \Closure|array|string|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this */ public function orWhereNot($column, $operator = null, $value = null) { return $this->whereNot($column, $operator, $value, 'or'); } /** * Add an "order by" clause for a timestamp to the query. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return $this */ public function latest($column = null) { if (is_null($column)) { $column = $this->model->getCreatedAtColumn() ?? 'created_at'; } $this->query->latest($column); return $this; } /** * Add an "order by" clause for a timestamp to the query. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return $this */ public function oldest($column = null) { if (is_null($column)) { $column = $this->model->getCreatedAtColumn() ?? 'created_at'; } $this->query->oldest($column); return $this; } /** * Create a collection of models from plain arrays. * * @param array $items * @return \Illuminate\Database\Eloquent\Collection */ public function hydrate(array $items) { $instance = $this->newModelInstance(); return $instance->newCollection(array_map(function ($item) use ($items, $instance) { $model = $instance->newFromBuilder($item); if (count($items) > 1) { $model->preventsLazyLoading = Model::preventsLazyLoading(); } return $model; }, $items)); } /** * Create a collection of models from a raw query. * * @param string $query * @param array $bindings * @return \Illuminate\Database\Eloquent\Collection */ public function fromQuery($query, $bindings = []) { return $this->hydrate( $this->query->getConnection()->select($query, $bindings) ); } /** * Find a model by its primary key. * * @param mixed $id * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null */ public function find($id, $columns = ['*']) { if (is_array($id) || $id instanceof Arrayable) { return $this->findMany($id, $columns); } return $this->whereKey($id)->first($columns); } /** * Find multiple models by their primary keys. * * @param \Illuminate\Contracts\Support\Arrayable|array $ids * @param array|string $columns * @return \Illuminate\Database\Eloquent\Collection */ public function findMany($ids, $columns = ['*']) { $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; if (empty($ids)) { return $this->model->newCollection(); } return $this->whereKey($ids)->get($columns); } /** * Find a model by its primary key or throw an exception. * * @param mixed $id * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[] * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> */ public function findOrFail($id, $columns = ['*']) { $result = $this->find($id, $columns); $id = $id instanceof Arrayable ? $id->toArray() : $id; if (is_array($id)) { if (count($result) !== count(array_unique($id))) { throw (new ModelNotFoundException)->setModel( get_class($this->model), array_diff($id, $result->modelKeys()) ); } return $result; } if (is_null($result)) { throw (new ModelNotFoundException)->setModel( get_class($this->model), $id ); } return $result; } /** * Find a model by its primary key or return fresh model instance. * * @param mixed $id * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|static */ public function findOrNew($id, $columns = ['*']) { if (! is_null($model = $this->find($id, $columns))) { return $model; } return $this->newModelInstance(); } /** * Find a model by its primary key or call a callback. * * @param mixed $id * @param \Closure|array|string $columns * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|mixed */ public function findOr($id, $columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } if (! is_null($model = $this->find($id, $columns))) { return $model; } return $callback(); } /** * Get the first record matching the attributes or instantiate it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model|static */ public function firstOrNew(array $attributes = [], array $values = []) { if (! is_null($instance = $this->where($attributes)->first())) { return $instance; } return $this->newModelInstance(array_merge($attributes, $values)); } /** * Get the first record matching the attributes. If the record is not found, create it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model|static */ public function firstOrCreate(array $attributes = [], array $values = []) { if (! is_null($instance = (clone $this)->where($attributes)->first())) { return $instance; } return $this->createOrFirst($attributes, $values); } /** * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model|static */ public function createOrFirst(array $attributes = [], array $values = []) { try { return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); } catch (UniqueConstraintViolationException $e) { return $this->useWritePdo()->where($attributes)->first() ?? throw $e; } } /** * Create or update a record matching the attributes, and fill it with values. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model|static */ public function updateOrCreate(array $attributes, array $values = []) { return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) { if (! $instance->wasRecentlyCreated) { $instance->fill($values)->save(); } }); } /** * Execute the query and get the first result or throw an exception. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|static * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> */ public function firstOrFail($columns = ['*']) { if (! is_null($model = $this->first($columns))) { return $model; } throw (new ModelNotFoundException)->setModel(get_class($this->model)); } /** * Execute the query and get the first result or call a callback. * * @param \Closure|array|string $columns * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|static|mixed */ public function firstOr($columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } if (! is_null($model = $this->first($columns))) { return $model; } return $callback(); } /** * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function sole($columns = ['*']) { try { return $this->baseSole($columns); } catch (RecordsNotFoundException) { throw (new ModelNotFoundException)->setModel(get_class($this->model)); } } /** * Get a single column's value from the first result of a query. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return mixed */ public function value($column) { if ($result = $this->first([$column])) { $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column; return $result->{Str::afterLast($column, '.')}; } } /** * Get a single column's value from the first result of a query if it's the sole matching record. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return mixed * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function soleValue($column) { $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column; return $this->sole([$column])->{Str::afterLast($column, '.')}; } /** * Get a single column's value from the first result of the query or throw an exception. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return mixed * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> */ public function valueOrFail($column) { $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column; return $this->firstOrFail([$column])->{Str::afterLast($column, '.')}; } /** * Execute the query as a "select" statement. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Collection|static[] */ public function get($columns = ['*']) { $builder = $this->applyScopes(); // If we actually found models we will also eager load any relationships that // have been specified as needing to be eager loaded, which will solve the // n+1 query issue for the developers to avoid running a lot of queries. if (count($models = $builder->getModels($columns)) > 0) { $models = $builder->eagerLoadRelations($models); } return $builder->getModel()->newCollection($models); } /** * Get the hydrated models without eager loading. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model[]|static[] */ public function getModels($columns = ['*']) { return $this->model->hydrate( $this->query->get($columns)->all() )->all(); } /** * Eager load the relationships for the models. * * @param array $models * @return array */ public function eagerLoadRelations(array $models) { foreach ($this->eagerLoad as $name => $constraints) { // For nested eager loads we'll skip loading them here and they will be set as an // eager load on the query to retrieve the relation so that they will be eager // loaded on that query, because that is where they get hydrated as models. if (! str_contains($name, '.')) { $models = $this->eagerLoadRelation($models, $name, $constraints); } } return $models; } /** * Eagerly load the relationship on a set of models. * * @param array $models * @param string $name * @param \Closure $constraints * @return array */ protected function eagerLoadRelation(array $models, $name, Closure $constraints) { // First we will "back up" the existing where conditions on the query so we can // add our eager constraints. Then we will merge the wheres that were on the // query back to it in order that any where conditions might be specified. $relation = $this->getRelation($name); $relation->addEagerConstraints($models); $constraints($relation); // Once we have the results, we just match those back up to their parent models // using the relationship instance. Then we just return the finished arrays // of models which have been eagerly hydrated and are readied for return. return $relation->match( $relation->initRelation($models, $name), $relation->getEager(), $name ); } /** * Get the relation instance for the given relation name. * * @param string $name * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function getRelation($name) { // We want to run a relationship query without any constrains so that we will // not have to remove these where clauses manually which gets really hacky // and error prone. We don't want constraints because we add eager ones. $relation = Relation::noConstraints(function () use ($name) { try { return $this->getModel()->newInstance()->$name(); } catch (BadMethodCallException) { throw RelationNotFoundException::make($this->getModel(), $name); } }); $nested = $this->relationsNestedUnder($name); // If there are nested relationships set on the query, we will put those onto // the query instances so that they can be handled after this relationship // is loaded. In this way they will all trickle down as they are loaded. if (count($nested) > 0) { $relation->getQuery()->with($nested); } return $relation; } /** * Get the deeply nested relations for a given top-level relation. * * @param string $relation * @return array */ protected function relationsNestedUnder($relation) { $nested = []; // We are basically looking for any relationships that are nested deeper than // the given top-level relationship. We will just check for any relations // that start with the given top relations and adds them to our arrays. foreach ($this->eagerLoad as $name => $constraints) { if ($this->isNestedUnder($relation, $name)) { $nested[substr($name, strlen($relation.'.'))] = $constraints; } } return $nested; } /** * Determine if the relationship is nested. * * @param string $relation * @param string $name * @return bool */ protected function isNestedUnder($relation, $name) { return str_contains($name, '.') && str_starts_with($name, $relation.'.'); } /** * Get a lazy collection for the given query. * * @return \Illuminate\Support\LazyCollection */ public function cursor() { return $this->applyScopes()->query->cursor()->map(function ($record) { return $this->newModelInstance()->newFromBuilder($record); }); } /** * Add a generic "order by" clause if the query doesn't already have one. * * @return void */ protected function enforceOrderBy() { if (empty($this->query->orders) && empty($this->query->unionOrders)) { $this->orderBy($this->model->getQualifiedKeyName(), 'asc'); } } /** * Get a collection with the values of a given column. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @param string|null $key * @return \Illuminate\Support\Collection */ public function pluck($column, $key = null) { $results = $this->toBase()->pluck($column, $key); $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column; // If the model has a mutator for the requested column, we will spin through // the results and mutate the values so that the mutated version of these // columns are returned as you would expect from these Eloquent models. if (! $this->model->hasGetMutator($column) && ! $this->model->hasCast($column) && ! in_array($column, $this->model->getDates())) { return $results; } return $results->map(function ($value) use ($column) { return $this->model->newFromBuilder([$column => $value])->{$column}; }); } /** * Paginate the given query. * * @param int|null|\Closure $perPage * @param array|string $columns * @param string $pageName * @param int|null $page * @param \Closure|int|null $total * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator * * @throws \InvalidArgumentException */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { $page = $page ?: Paginator::resolveCurrentPage($pageName); $total = func_num_args() === 5 ? value(func_get_arg(4)) : $this->toBase()->getCountForPagination(); $perPage = ($perPage instanceof Closure ? $perPage($total) : $perPage ) ?: $this->model->getPerPage(); $results = $total ? $this->forPage($page, $perPage)->get($columns) : $this->model->newCollection(); return $this->paginator($results, $total, $perPage, $page, [ 'path' => Paginator::resolveCurrentPath(), 'pageName' => $pageName, ]); } /** * Paginate the given query into a simple paginator. * * @param int|null $perPage * @param array|string $columns * @param string $pageName * @param int|null $page * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { $page = $page ?: Paginator::resolveCurrentPage($pageName); $perPage = $perPage ?: $this->model->getPerPage(); // Next we will set the limit and offset for this query so that when we get the // results we get the proper section of results. Then, we'll create the full // paginator instances for these results with the given page and per page. $this->skip(($page - 1) * $perPage)->take($perPage + 1); return $this->simplePaginator($this->get($columns), $perPage, $page, [ 'path' => Paginator::resolveCurrentPath(), 'pageName' => $pageName, ]); } /** * Paginate the given query into a cursor paginator. * * @param int|null $perPage * @param array|string $columns * @param string $cursorName * @param \Illuminate\Pagination\Cursor|string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator */ public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { $perPage = $perPage ?: $this->model->getPerPage(); return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor); } /** * Ensure the proper order by required for cursor pagination. * * @param bool $shouldReverse * @return \Illuminate\Support\Collection */ protected function ensureOrderForCursorPagination($shouldReverse = false) { if (empty($this->query->orders) && empty($this->query->unionOrders)) { $this->enforceOrderBy(); } $reverseDirection = function ($order) { if (! isset($order['direction'])) { return $order; } $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc'; return $order; }; if ($shouldReverse) { $this->query->orders = collect($this->query->orders)->map($reverseDirection)->toArray(); $this->query->unionOrders = collect($this->query->unionOrders)->map($reverseDirection)->toArray(); } $orders = ! empty($this->query->unionOrders) ? $this->query->unionOrders : $this->query->orders; return collect($orders) ->filter(fn ($order) => Arr::has($order, 'direction')) ->values(); } /** * Save a new model and return the instance. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model|$this */ public function create(array $attributes = []) { return tap($this->newModelInstance($attributes), function ($instance) { $instance->save(); }); } /** * Save a new model and return the instance. Allow mass-assignment. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model|$this */ public function forceCreate(array $attributes) { return $this->model->unguarded(function () use ($attributes) { return $this->newModelInstance()->create($attributes); }); } /** * Save a new model instance with mass assignment without raising model events. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model|$this */ public function forceCreateQuietly(array $attributes = []) { return Model::withoutEvents(fn () => $this->forceCreate($attributes)); } /** * Update records in the database. * * @param array $values * @return int */ public function update(array $values) { return $this->toBase()->update($this->addUpdatedAtColumn($values)); } /** * Insert new records or update the existing ones. * * @param array $values * @param array|string $uniqueBy * @param array|null $update * @return int */ public function upsert(array $values, $uniqueBy, $update = null) { if (empty($values)) { return 0; } if (! is_array(reset($values))) { $values = [$values]; } if (is_null($update)) { $update = array_keys(reset($values)); } return $this->toBase()->upsert( $this->addTimestampsToUpsertValues($this->addUniqueIdsToUpsertValues($values)), $uniqueBy, $this->addUpdatedAtToUpsertColumns($update) ); } /** * Update the column's update timestamp. * * @param string|null $column * @return int|false */ public function touch($column = null) { $time = $this->model->freshTimestamp(); if ($column) { return $this->toBase()->update([$column => $time]); } $column = $this->model->getUpdatedAtColumn(); if (! $this->model->usesTimestamps() || is_null($column)) { return false; } return $this->toBase()->update([$column => $time]); } /** * Increment a column's value by a given amount. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @param float|int $amount * @param array $extra * @return int */ public function increment($column, $amount = 1, array $extra = []) { return $this->toBase()->increment( $column, $amount, $this->addUpdatedAtColumn($extra) ); } /** * Decrement a column's value by a given amount. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @param float|int $amount * @param array $extra * @return int */ public function decrement($column, $amount = 1, array $extra = []) { return $this->toBase()->decrement( $column, $amount, $this->addUpdatedAtColumn($extra) ); } /** * Add the "updated at" column to an array of values. * * @param array $values * @return array */ protected function addUpdatedAtColumn(array $values) { if (! $this->model->usesTimestamps() || is_null($this->model->getUpdatedAtColumn())) { return $values; } $column = $this->model->getUpdatedAtColumn(); if (! array_key_exists($column, $values)) { $timestamp = $this->model->freshTimestampString(); if ( $this->model->hasSetMutator($column) || $this->model->hasAttributeSetMutator($column) || $this->model->hasCast($column) ) { $timestamp = $this->model->newInstance() ->forceFill([$column => $timestamp]) ->getAttributes()[$column] ?? $timestamp; } $values = array_merge([$column => $timestamp], $values); } $segments = preg_split('/\s+as\s+/i', $this->query->from); $qualifiedColumn = end($segments).'.'.$column; $values[$qualifiedColumn] = Arr::get($values, $qualifiedColumn, $values[$column]); unset($values[$column]); return $values; } /** * Add unique IDs to the inserted values. * * @param array $values * @return array */ protected function addUniqueIdsToUpsertValues(array $values) { if (! $this->model->usesUniqueIds()) { return $values; } foreach ($this->model->uniqueIds() as $uniqueIdAttribute) { foreach ($values as &$row) { if (! array_key_exists($uniqueIdAttribute, $row)) { $row = array_merge([$uniqueIdAttribute => $this->model->newUniqueId()], $row); } } } return $values; } /** * Add timestamps to the inserted values. * * @param array $values * @return array */ protected function addTimestampsToUpsertValues(array $values) { if (! $this->model->usesTimestamps()) { return $values; } $timestamp = $this->model->freshTimestampString(); $columns = array_filter([ $this->model->getCreatedAtColumn(), $this->model->getUpdatedAtColumn(), ]); foreach ($columns as $column) { foreach ($values as &$row) { $row = array_merge([$column => $timestamp], $row); } } return $values; } /** * Add the "updated at" column to the updated columns. * * @param array $update * @return array */ protected function addUpdatedAtToUpsertColumns(array $update) { if (! $this->model->usesTimestamps()) { return $update; } $column = $this->model->getUpdatedAtColumn(); if (! is_null($column) && ! array_key_exists($column, $update) && ! in_array($column, $update)) { $update[] = $column; } return $update; } /** * Delete records from the database. * * @return mixed */ public function delete() { if (isset($this->onDelete)) { return call_user_func($this->onDelete, $this); } return $this->toBase()->delete(); } /** * Run the default delete function on the builder. * * Since we do not apply scopes here, the row will actually be deleted. * * @return mixed */ public function forceDelete() { return $this->query->delete(); } /** * Register a replacement for the default delete function. * * @param \Closure $callback * @return void */ public function onDelete(Closure $callback) { $this->onDelete = $callback; } /** * Determine if the given model has a scope. * * @param string $scope * @return bool */ public function hasNamedScope($scope) { return $this->model && $this->model->hasNamedScope($scope); } /** * Call the given local model scopes. * * @param array|string $scopes * @return static|mixed */ public function scopes($scopes) { $builder = $this; foreach (Arr::wrap($scopes) as $scope => $parameters) { // If the scope key is an integer, then the scope was passed as the value and // the parameter list is empty, so we will format the scope name and these // parameters here. Then, we'll be ready to call the scope on the model. if (is_int($scope)) { [$scope, $parameters] = [$parameters, []]; } // Next we'll pass the scope callback to the callScope method which will take // care of grouping the "wheres" properly so the logical order doesn't get // messed up when adding scopes. Then we'll return back out the builder. $builder = $builder->callNamedScope( $scope, Arr::wrap($parameters) ); } return $builder; } /** * Apply the scopes to the Eloquent builder instance and return it. * * @return static */ public function applyScopes() { if (! $this->scopes) { return $this; } $builder = clone $this; foreach ($this->scopes as $identifier => $scope) { if (! isset($builder->scopes[$identifier])) { continue; } $builder->callScope(function (self $builder) use ($scope) { // If the scope is a Closure we will just go ahead and call the scope with the // builder instance. The "callScope" method will properly group the clauses // that are added to this query so "where" clauses maintain proper logic. if ($scope instanceof Closure) { $scope($builder); } // If the scope is a scope object, we will call the apply method on this scope // passing in the builder and the model instance. After we run all of these // scopes we will return back the builder instance to the outside caller. if ($scope instanceof Scope) { $scope->apply($builder, $this->getModel()); } }); } return $builder; } /** * Apply the given scope on the current builder instance. * * @param callable $scope * @param array $parameters * @return mixed */ protected function callScope(callable $scope, array $parameters = []) { array_unshift($parameters, $this); $query = $this->getQuery(); // We will keep track of how many wheres are on the query before running the // scope so that we can properly group the added scope constraints in the // query as their own isolated nested where statement and avoid issues. $originalWhereCount = is_null($query->wheres) ? 0 : count($query->wheres); $result = $scope(...$parameters) ?? $this; if (count((array) $query->wheres) > $originalWhereCount) { $this->addNewWheresWithinGroup($query, $originalWhereCount); } return $result; } /** * Apply the given named scope on the current builder instance. * * @param string $scope * @param array $parameters * @return mixed */ protected function callNamedScope($scope, array $parameters = []) { return $this->callScope(function (...$parameters) use ($scope) { return $this->model->callNamedScope($scope, $parameters); }, $parameters); } /** * Nest where conditions by slicing them at the given where count. * * @param \Illuminate\Database\Query\Builder $query * @param int $originalWhereCount * @return void */ protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCount) { // Here, we totally remove all of the where clauses since we are going to // rebuild them as nested queries by slicing the groups of wheres into // their own sections. This is to prevent any confusing logic order. $allWheres = $query->wheres; $query->wheres = []; $this->groupWhereSliceForScope( $query, array_slice($allWheres, 0, $originalWhereCount) ); $this->groupWhereSliceForScope( $query, array_slice($allWheres, $originalWhereCount) ); } /** * Slice where conditions at the given offset and add them to the query as a nested condition. * * @param \Illuminate\Database\Query\Builder $query * @param array $whereSlice * @return void */ protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice) { $whereBooleans = collect($whereSlice)->pluck('boolean'); // Here we'll check if the given subset of where clauses contains any "or" // booleans and in this case create a nested where expression. That way // we don't add any unnecessary nesting thus keeping the query clean. if ($whereBooleans->contains('or')) { $query->wheres[] = $this->createNestedWhere( $whereSlice, $whereBooleans->first() ); } else { $query->wheres = array_merge($query->wheres, $whereSlice); } } /** * Create a where array with nested where conditions. * * @param array $whereSlice * @param string $boolean * @return array */ protected function createNestedWhere($whereSlice, $boolean = 'and') { $whereGroup = $this->getQuery()->forNestedWhere(); $whereGroup->wheres = $whereSlice; return ['type' => 'Nested', 'query' => $whereGroup, 'boolean' => $boolean]; } /** * Set the relationships that should be eager loaded. * * @param string|array $relations * @param string|\Closure|null $callback * @return $this */ public function with($relations, $callback = null) { if ($callback instanceof Closure) { $eagerLoad = $this->parseWithRelations([$relations => $callback]); } else { $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations); } $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad); return $this; } /** * Prevent the specified relations from being eager loaded. * * @param mixed $relations * @return $this */ public function without($relations) { $this->eagerLoad = array_diff_key($this->eagerLoad, array_flip( is_string($relations) ? func_get_args() : $relations )); return $this; } /** * Set the relationships that should be eager loaded while removing any previously added eager loading specifications. * * @param mixed $relations * @return $this */ public function withOnly($relations) { $this->eagerLoad = []; return $this->with($relations); } /** * Create a new instance of the model being queried. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model|static */ public function newModelInstance($attributes = []) { return $this->model->newInstance($attributes)->setConnection( $this->query->getConnection()->getName() ); } /** * Parse a list of relations into individuals. * * @param array $relations * @return array */ protected function parseWithRelations(array $relations) { if ($relations === []) { return []; } $results = []; foreach ($this->prepareNestedWithRelationships($relations) as $name => $constraints) { // We need to separate out any nested includes, which allows the developers // to load deep relationships using "dots" without stating each level of // the relationship with its own key in the array of eager-load names. $results = $this->addNestedWiths($name, $results); $results[$name] = $constraints; } return $results; } /** * Prepare nested with relationships. * * @param array $relations * @param string $prefix * @return array */ protected function prepareNestedWithRelationships($relations, $prefix = '') { $preparedRelationships = []; if ($prefix !== '') { $prefix .= '.'; } // If any of the relationships are formatted with the [$attribute => array()] // syntax, we shall loop over the nested relations and prepend each key of // this array while flattening into the traditional dot notation format. foreach ($relations as $key => $value) { if (! is_string($key) || ! is_array($value)) { continue; } [$attribute, $attributeSelectConstraint] = $this->parseNameAndAttributeSelectionConstraint($key); $preparedRelationships = array_merge( $preparedRelationships, ["{$prefix}{$attribute}" => $attributeSelectConstraint], $this->prepareNestedWithRelationships($value, "{$prefix}{$attribute}"), ); unset($relations[$key]); } // We now know that the remaining relationships are in a dot notation format // and may be a string or Closure. We'll loop over them and ensure all of // the present Closures are merged + strings are made into constraints. foreach ($relations as $key => $value) { if (is_numeric($key) && is_string($value)) { [$key, $value] = $this->parseNameAndAttributeSelectionConstraint($value); } $preparedRelationships[$prefix.$key] = $this->combineConstraints([ $value, $preparedRelationships[$prefix.$key] ?? static function () { // }, ]); } return $preparedRelationships; } /** * Combine an array of constraints into a single constraint. * * @param array $constraints * @return \Closure */ protected function combineConstraints(array $constraints) { return function ($builder) use ($constraints) { foreach ($constraints as $constraint) { $builder = $constraint($builder) ?? $builder; } return $builder; }; } /** * Parse the attribute select constraints from the name. * * @param string $name * @return array */ protected function parseNameAndAttributeSelectionConstraint($name) { return str_contains($name, ':') ? $this->createSelectWithConstraint($name) : [$name, static function () { // }]; } /** * Create a constraint to select the given columns for the relation. * * @param string $name * @return array */ protected function createSelectWithConstraint($name) { return [explode(':', $name)[0], static function ($query) use ($name) { $query->select(array_map(static function ($column) use ($query) { if (str_contains($column, '.')) { return $column; } return $query instanceof BelongsToMany ? $query->getRelated()->getTable().'.'.$column : $column; }, explode(',', explode(':', $name)[1]))); }]; } /** * Parse the nested relationships in a relation. * * @param string $name * @param array $results * @return array */ protected function addNestedWiths($name, $results) { $progress = []; // If the relation has already been set on the result array, we will not set it // again, since that would override any constraints that were already placed // on the relationships. We will only set the ones that are not specified. foreach (explode('.', $name) as $segment) { $progress[] = $segment; if (! isset($results[$last = implode('.', $progress)])) { $results[$last] = static function () { // }; } } return $results; } /** * Apply query-time casts to the model instance. * * @param array $casts * @return $this */ public function withCasts($casts) { $this->model->mergeCasts($casts); return $this; } /** * Execute the given Closure within a transaction savepoint if needed. * * @template TModelValue * * @param \Closure(): TModelValue $scope * @return TModelValue */ public function withSavepointIfNeeded(Closure $scope): mixed { return $this->getQuery()->getConnection()->transactionLevel() > 0 ? $this->getQuery()->getConnection()->transaction($scope) : $scope(); } /** * Get the underlying query builder instance. * * @return \Illuminate\Database\Query\Builder */ public function getQuery() { return $this->query; } /** * Set the underlying query builder instance. * * @param \Illuminate\Database\Query\Builder $query * @return $this */ public function setQuery($query) { $this->query = $query; return $this; } /** * Get a base query builder instance. * * @return \Illuminate\Database\Query\Builder */ public function toBase() { return $this->applyScopes()->getQuery(); } /** * Get the relationships being eagerly loaded. * * @return array */ public function getEagerLoads() { return $this->eagerLoad; } /** * Set the relationships being eagerly loaded. * * @param array $eagerLoad * @return $this */ public function setEagerLoads(array $eagerLoad) { $this->eagerLoad = $eagerLoad; return $this; } /** * Indicate that the given relationships should not be eagerly loaded. * * @param array $relations * @return $this */ public function withoutEagerLoad(array $relations) { $relations = array_diff(array_keys($this->model->getRelations()), $relations); return $this->with($relations); } /** * Flush the relationships being eagerly loaded. * * @return $this */ public function withoutEagerLoads() { return $this->setEagerLoads([]); } /** * Get the default key name of the table. * * @return string */ protected function defaultKeyName() { return $this->getModel()->getKeyName(); } /** * Get the model instance being queried. * * @return \Illuminate\Database\Eloquent\Model|static */ public function getModel() { return $this->model; } /** * Set a model instance for the model being queried. * * @param \Illuminate\Database\Eloquent\Model $model * @return $this */ public function setModel(Model $model) { $this->model = $model; $this->query->from($model->getTable()); return $this; } /** * Qualify the given column name by the model's table. * * @param string|\Illuminate\Contracts\Database\Query\Expression $column * @return string */ public function qualifyColumn($column) { $column = $column instanceof Expression ? $column->getValue($this->getGrammar()) : $column; return $this->model->qualifyColumn($column); } /** * Qualify the given columns with the model's table. * * @param array|\Illuminate\Contracts\Database\Query\Expression $columns * @return array */ public function qualifyColumns($columns) { return $this->model->qualifyColumns($columns); } /** * Get the given macro by name. * * @param string $name * @return \Closure */ public function getMacro($name) { return Arr::get($this->localMacros, $name); } /** * Checks if a macro is registered. * * @param string $name * @return bool */ public function hasMacro($name) { return isset($this->localMacros[$name]); } /** * Get the given global macro by name. * * @param string $name * @return \Closure */ public static function getGlobalMacro($name) { return Arr::get(static::$macros, $name); } /** * Checks if a global macro is registered. * * @param string $name * @return bool */ public static function hasGlobalMacro($name) { return isset(static::$macros[$name]); } /** * Dynamically access builder proxies. * * @param string $key * @return mixed * * @throws \Exception */ public function __get($key) { if (in_array($key, ['orWhere', 'whereNot', 'orWhereNot'])) { return new HigherOrderBuilderProxy($this, $key); } if (in_array($key, $this->propertyPassthru)) { return $this->toBase()->{$key}; } throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance."); } /** * Dynamically handle calls into the query instance. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if ($method === 'macro') { $this->localMacros[$parameters[0]] = $parameters[1]; return; } if ($this->hasMacro($method)) { array_unshift($parameters, $this); return $this->localMacros[$method](...$parameters); } if (static::hasGlobalMacro($method)) { $callable = static::$macros[$method]; if ($callable instanceof Closure) { $callable = $callable->bindTo($this, static::class); } return $callable(...$parameters); } if ($this->hasNamedScope($method)) { return $this->callNamedScope($method, $parameters); } if (in_array($method, $this->passthru)) { return $this->toBase()->{$method}(...$parameters); } $this->forwardCallTo($this->query, $method, $parameters); return $this; } /** * Dynamically handle calls into the query instance. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public static function __callStatic($method, $parameters) { if ($method === 'macro') { static::$macros[$parameters[0]] = $parameters[1]; return; } if ($method === 'mixin') { return static::registerMixin($parameters[0], $parameters[1] ?? true); } if (! static::hasGlobalMacro($method)) { static::throwBadMethodCallException($method); } $callable = static::$macros[$method]; if ($callable instanceof Closure) { $callable = $callable->bindTo(null, static::class); } return $callable(...$parameters); } /** * Register the given mixin with the builder. * * @param string $mixin * @param bool $replace * @return void */ protected static function registerMixin($mixin, $replace) { $methods = (new ReflectionClass($mixin))->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED ); foreach ($methods as $method) { if ($replace || ! static::hasGlobalMacro($method->name)) { static::macro($method->name, $method->invoke($mixin)); } } } /** * Clone the Eloquent query builder. * * @return static */ public function clone() { return clone $this; } /** * Force a clone of the underlying query builder when cloning. * * @return void */ public function __clone() { $this->query = clone $this->query; } } Eloquent/Casts/ArrayObject.php000064400000001616147536122140012344 0ustar00 */ class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable { /** * Get a collection containing the underlying array. * * @return \Illuminate\Support\Collection */ public function collect() { return collect($this->getArrayCopy()); } /** * Get the instance as an array. * * @return array */ public function toArray() { return $this->getArrayCopy(); } /** * Get the array that should be JSON serialized. * * @return array */ public function jsonSerialize(): array { return $this->getArrayCopy(); } } Eloquent/Casts/AsArrayObject.php000064400000002363147536122140012630 0ustar00, iterable> */ public static function castUsing(array $arguments) { return new class implements CastsAttributes { public function get($model, $key, $value, $attributes) { if (! isset($attributes[$key])) { return; } $data = Json::decode($attributes[$key]); return is_array($data) ? new ArrayObject($data) : null; } public function set($model, $key, $value, $attributes) { return [$key => Json::encode($value)]; } public function serialize($model, string $key, $value, array $attributes) { return $value->getArrayCopy(); } }; } } Eloquent/Casts/AsCollection.php000064400000003017147536122140012513 0ustar00, iterable> */ public static function castUsing(array $arguments) { return new class($arguments) implements CastsAttributes { public function __construct(protected array $arguments) { } public function get($model, $key, $value, $attributes) { if (! isset($attributes[$key])) { return; } $data = Json::decode($attributes[$key]); $collectionClass = $this->arguments[0] ?? Collection::class; if (! is_a($collectionClass, Collection::class, true)) { throw new InvalidArgumentException('The provided class must extend ['.Collection::class.'].'); } return is_array($data) ? new $collectionClass($data) : null; } public function set($model, $key, $value, $attributes) { return [$key => Json::encode($value)]; } }; } } Eloquent/Casts/AsEncryptedArrayObject.php000064400000002620147536122140014502 0ustar00, iterable> */ public static function castUsing(array $arguments) { return new class implements CastsAttributes { public function get($model, $key, $value, $attributes) { if (isset($attributes[$key])) { return new ArrayObject(Json::decode(Crypt::decryptString($attributes[$key]))); } return null; } public function set($model, $key, $value, $attributes) { if (! is_null($value)) { return [$key => Crypt::encryptString(Json::encode($value))]; } return null; } public function serialize($model, string $key, $value, array $attributes) { return ! is_null($value) ? $value->getArrayCopy() : null; } }; } } Eloquent/Casts/AsEncryptedCollection.php000064400000003221147536122140014366 0ustar00, iterable> */ public static function castUsing(array $arguments) { return new class($arguments) implements CastsAttributes { public function __construct(protected array $arguments) { } public function get($model, $key, $value, $attributes) { $collectionClass = $this->arguments[0] ?? Collection::class; if (! is_a($collectionClass, Collection::class, true)) { throw new InvalidArgumentException('The provided class must extend ['.Collection::class.'].'); } if (isset($attributes[$key])) { return new $collectionClass(Json::decode(Crypt::decryptString($attributes[$key]))); } return null; } public function set($model, $key, $value, $attributes) { if (! is_null($value)) { return [$key => Crypt::encryptString(Json::encode($value))]; } return null; } }; } } Eloquent/Casts/AsEnumArrayObject.php000064400000005147147536122140013460 0ustar00} $arguments * @return \Illuminate\Contracts\Database\Eloquent\CastsAttributes<\Illuminate\Database\Eloquent\Casts\ArrayObject, iterable> */ public static function castUsing(array $arguments) { return new class($arguments) implements CastsAttributes { protected $arguments; public function __construct(array $arguments) { $this->arguments = $arguments; } public function get($model, $key, $value, $attributes) { if (! isset($attributes[$key]) || is_null($attributes[$key])) { return; } $data = Json::decode($attributes[$key]); if (! is_array($data)) { return; } $enumClass = $this->arguments[0]; return new ArrayObject((new Collection($data))->map(function ($value) use ($enumClass) { return is_subclass_of($enumClass, BackedEnum::class) ? $enumClass::from($value) : constant($enumClass.'::'.$value); })->toArray()); } public function set($model, $key, $value, $attributes) { if ($value === null) { return [$key => null]; } $storable = []; foreach ($value as $enum) { $storable[] = $this->getStorableEnumValue($enum); } return [$key => Json::encode($storable)]; } public function serialize($model, string $key, $value, array $attributes) { return (new Collection($value->getArrayCopy()))->map(function ($enum) { return $this->getStorableEnumValue($enum); })->toArray(); } protected function getStorableEnumValue($enum) { if (is_string($enum) || is_int($enum)) { return $enum; } return $enum instanceof BackedEnum ? $enum->value : $enum->name; } }; } } Eloquent/Casts/AsEnumCollection.php000064400000005054147536122140013343 0ustar00} $arguments * @return \Illuminate\Contracts\Database\Eloquent\CastsAttributes<\Illuminate\Support\Collection, iterable> */ public static function castUsing(array $arguments) { return new class($arguments) implements CastsAttributes { protected $arguments; public function __construct(array $arguments) { $this->arguments = $arguments; } public function get($model, $key, $value, $attributes) { if (! isset($attributes[$key]) || is_null($attributes[$key])) { return; } $data = Json::decode($attributes[$key]); if (! is_array($data)) { return; } $enumClass = $this->arguments[0]; return (new Collection($data))->map(function ($value) use ($enumClass) { return is_subclass_of($enumClass, BackedEnum::class) ? $enumClass::from($value) : constant($enumClass.'::'.$value); }); } public function set($model, $key, $value, $attributes) { $value = $value !== null ? Json::encode((new Collection($value))->map(function ($enum) { return $this->getStorableEnumValue($enum); })->jsonSerialize()) : null; return [$key => $value]; } public function serialize($model, string $key, $value, array $attributes) { return (new Collection($value))->map(function ($enum) { return $this->getStorableEnumValue($enum); })->toArray(); } protected function getStorableEnumValue($enum) { if (is_string($enum) || is_int($enum)) { return $enum; } return $enum instanceof BackedEnum ? $enum->value : $enum->name; } }; } } Eloquent/Casts/AsStringable.php000064400000001667147536122140012523 0ustar00 */ public static function castUsing(array $arguments) { return new class implements CastsAttributes { public function get($model, $key, $value, $attributes) { return isset($value) ? Str::of($value) : null; } public function set($model, $key, $value, $attributes) { return isset($value) ? (string) $value : null; } }; } } Eloquent/Casts/Attribute.php000064400000003664147536122140012107 0ustar00get = $get; $this->set = $set; } /** * Create a new attribute accessor / mutator. * * @param callable|null $get * @param callable|null $set * @return static */ public static function make(callable $get = null, callable $set = null): static { return new static($get, $set); } /** * Create a new attribute accessor. * * @param callable $get * @return static */ public static function get(callable $get) { return new static($get); } /** * Create a new attribute mutator. * * @param callable $set * @return static */ public static function set(callable $set) { return new static(null, $set); } /** * Disable object caching for the attribute. * * @return static */ public function withoutObjectCaching() { $this->withObjectCaching = false; return $this; } /** * Enable caching for the attribute. * * @return static */ public function shouldCache() { $this->withCaching = true; return $this; } } Eloquent/Casts/Json.php000064400000002204147536122140011042 0ustar00 */ class Collection extends BaseCollection implements QueueableCollection { use InteractsWithDictionary; /** * Find a model in the collection by key. * * @template TFindDefault * * @param mixed $key * @param TFindDefault $default * @return static|TModel|TFindDefault */ 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, fn ($model) => $model->getKey() == $key, $default); } /** * Load a set of relationships onto the collection. * * @param array|string $relations * @return $this */ public function load($relations) { if ($this->isNotEmpty()) { if (is_string($relations)) { $relations = func_get_args(); } $query = $this->first()->newQueryWithoutRelationships()->with($relations); $this->items = $query->eagerLoadRelations($this->items); } return $this; } /** * Load a set of aggregations over relationship's column onto the collection. * * @param array|string $relations * @param string $column * @param string|null $function * @return $this */ public function loadAggregate($relations, $column, $function = null) { if ($this->isEmpty()) { return $this; } $models = $this->first()->newModelQuery() ->whereKey($this->modelKeys()) ->select($this->first()->getKeyName()) ->withAggregate($relations, $column, $function) ->get() ->keyBy($this->first()->getKeyName()); $attributes = Arr::except( array_keys($models->first()->getAttributes()), $models->first()->getKeyName() ); $this->each(function ($model) use ($models, $attributes) { $extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes); $model->forceFill($extraAttributes) ->syncOriginalAttributes($attributes) ->mergeCasts($models->get($model->getKey())->getCasts()); }); return $this; } /** * Load a set of relationship counts onto the collection. * * @param array|string $relations * @return $this */ public function loadCount($relations) { return $this->loadAggregate($relations, '*', 'count'); } /** * Load a set of relationship's max column values onto the collection. * * @param array|string $relations * @param string $column * @return $this */ public function loadMax($relations, $column) { return $this->loadAggregate($relations, $column, 'max'); } /** * Load a set of relationship's min column values onto the collection. * * @param array|string $relations * @param string $column * @return $this */ public function loadMin($relations, $column) { return $this->loadAggregate($relations, $column, 'min'); } /** * Load a set of relationship's column summations onto the collection. * * @param array|string $relations * @param string $column * @return $this */ public function loadSum($relations, $column) { return $this->loadAggregate($relations, $column, 'sum'); } /** * Load a set of relationship's average column values onto the collection. * * @param array|string $relations * @param string $column * @return $this */ public function loadAvg($relations, $column) { return $this->loadAggregate($relations, $column, 'avg'); } /** * Load a set of related existences onto the collection. * * @param array|string $relations * @return $this */ public function loadExists($relations) { return $this->loadAggregate($relations, '*', 'exists'); } /** * Load a set of relationships onto the collection if they are not already eager loaded. * * @param array|string $relations * @return $this */ public function loadMissing($relations) { if (is_string($relations)) { $relations = func_get_args(); } foreach ($relations as $key => $value) { if (is_numeric($key)) { $key = $value; } $segments = explode('.', explode(':', $key)[0]); if (str_contains($key, ':')) { $segments[count($segments) - 1] .= ':'.explode(':', $key)[1]; } $path = []; foreach ($segments as $segment) { $path[] = [$segment => $segment]; } if (is_callable($value)) { $path[count($segments) - 1][end($segments)] = $value; } $this->loadMissingRelation($this, $path); } return $this; } /** * Load a relationship path if it is not already eager loaded. * * @param \Illuminate\Database\Eloquent\Collection $models * @param array $path * @return void */ protected function loadMissingRelation(self $models, array $path) { $relation = array_shift($path); $name = explode(':', key($relation))[0]; if (is_string(reset($relation))) { $relation = reset($relation); } $models->filter(fn ($model) => ! is_null($model) && ! $model->relationLoaded($name))->load($relation); if (empty($path)) { return; } $models = $models->pluck($name)->whereNotNull(); if ($models->first() instanceof BaseCollection) { $models = $models->collapse(); } $this->loadMissingRelation(new static($models), $path); } /** * Load a set of relationships onto the mixed relationship collection. * * @param string $relation * @param array $relations * @return $this */ public function loadMorph($relation, $relations) { $this->pluck($relation) ->filter() ->groupBy(fn ($model) => get_class($model)) ->each(fn ($models, $className) => static::make($models)->load($relations[$className] ?? [])); return $this; } /** * Load a set of relationship counts onto the mixed relationship collection. * * @param string $relation * @param array $relations * @return $this */ public function loadMorphCount($relation, $relations) { $this->pluck($relation) ->filter() ->groupBy(fn ($model) => get_class($model)) ->each(fn ($models, $className) => static::make($models)->loadCount($relations[$className] ?? [])); return $this; } /** * Determine if a key exists in the collection. * * @param (callable(TModel, TKey): bool)|TModel|string|int $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(fn ($model) => $model->is($key)); } return parent::contains(fn ($model) => $model->getKey() == $key); } /** * Get the array of primary keys. * * @return array */ public function modelKeys() { return array_map(fn ($model) => $model->getKey(), $this->items); } /** * Merge the collection with the given items. * * @param iterable $items * @return static */ public function merge($items) { $dictionary = $this->getDictionary(); foreach ($items as $item) { $dictionary[$this->getDictionaryKey($item->getKey())] = $item; } return new static(array_values($dictionary)); } /** * Run a map over each of the items. * * @template TMapValue * * @param callable(TModel, TKey): TMapValue $callback * @return \Illuminate\Support\Collection|static */ public function map(callable $callback) { $result = parent::map($callback); return $result->contains(fn ($item) => ! $item instanceof Model) ? $result->toBase() : $result; } /** * Run an associative map over each of the items. * * The callback should return an associative array with a single key / value pair. * * @template TMapWithKeysKey of array-key * @template TMapWithKeysValue * * @param callable(TModel, TKey): array $callback * @return \Illuminate\Support\Collection|static */ public function mapWithKeys(callable $callback) { $result = parent::mapWithKeys($callback); return $result->contains(fn ($item) => ! $item instanceof Model) ? $result->toBase() : $result; } /** * Reload a fresh model instance from the database for all the entities. * * @param array|string $with * @return static */ public function fresh($with = []) { if ($this->isEmpty()) { return new static; } $model = $this->first(); $freshModels = $model->newQueryWithoutScopes() ->with(is_string($with) ? func_get_args() : $with) ->whereIn($model->getKeyName(), $this->modelKeys()) ->get() ->getDictionary(); return $this->filter(fn ($model) => $model->exists && isset($freshModels[$model->getKey()])) ->map(fn ($model) => $freshModels[$model->getKey()]); } /** * Diff the collection with the given items. * * @param iterable $items * @return static */ public function diff($items) { $diff = new static; $dictionary = $this->getDictionary($items); foreach ($this->items as $item) { if (! isset($dictionary[$this->getDictionaryKey($item->getKey())])) { $diff->add($item); } } return $diff; } /** * Intersect the collection with the given items. * * @param iterable $items * @return static */ public function intersect($items) { $intersect = new static; if (empty($items)) { return $intersect; } $dictionary = $this->getDictionary($items); foreach ($this->items as $item) { if (isset($dictionary[$this->getDictionaryKey($item->getKey())])) { $intersect->add($item); } } return $intersect; } /** * Return only unique items from the collection. * * @param (callable(TModel, TKey): mixed)|string|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 array|null $keys * @return static */ public function only($keys) { if (is_null($keys)) { return new static($this->items); } $dictionary = Arr::only($this->getDictionary(), array_map($this->getDictionaryKey(...), (array) $keys)); return new static(array_values($dictionary)); } /** * Returns all models in the collection except the models with specified keys. * * @param array|null $keys * @return static */ public function except($keys) { if (is_null($keys)) { return new static($this->items); } $dictionary = Arr::except($this->getDictionary(), array_map($this->getDictionaryKey(...), (array) $keys)); 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); } /** * Set the visible attributes across the entire collection. * * @param array $visible * @return $this */ public function setVisible($visible) { return $this->each->setVisible($visible); } /** * Set the hidden attributes across the entire collection. * * @param array $hidden * @return $this */ public function setHidden($hidden) { return $this->each->setHidden($hidden); } /** * 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 iterable|null $items * @return array */ public function getDictionary($items = null) { $items = is_null($items) ? $this->items : $items; $dictionary = []; foreach ($items as $value) { $dictionary[$this->getDictionaryKey($value->getKey())] = $value; } return $dictionary; } /** * The following methods are intercepted to always return base collections. */ /** * Count the number of items in the collection by a field or using a callback. * * @param (callable(TModel, TKey): array-key)|string|null $countBy * @return \Illuminate\Support\Collection */ public function countBy($countBy = null) { return $this->toBase()->countBy($countBy); } /** * 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(); } /** * Get the keys of the collection items. * * @return \Illuminate\Support\Collection */ public function keys() { return $this->toBase()->keys(); } /** * Pad collection to the specified length with a value. * * @template TPadValue * * @param int $size * @param TPadValue $value * @return \Illuminate\Support\Collection */ public function pad($size, $value) { return $this->toBase()->pad($size, $value); } /** * 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); } /** * Zip the collection together with one or more arrays. * * @template TZipValue * * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$items * @return \Illuminate\Support\Collection> */ public function zip($items) { return $this->toBase()->zip(...func_get_args()); } /** * Get the comparison function to detect duplicates. * * @param bool $strict * @return callable(TModel, TModel): bool */ protected function duplicateComparator($strict) { return fn ($a, $b) => $a->is($b); } /** * Get the type of the entities being queued. * * @return string|null * * @throws \LogicException */ public function getQueueableClass() { if ($this->isEmpty()) { return; } $class = $this->getQueueableModelClass($this->first()); $this->each(function ($model) use ($class) { if ($this->getQueueableModelClass($model) !== $class) { throw new LogicException('Queueing collections with multiple model types is not supported.'); } }); return $class; } /** * Get the queueable class name for the given model. * * @param \Illuminate\Database\Eloquent\Model $model * @return string */ protected function getQueueableModelClass($model) { return method_exists($model, 'getQueueableClassName') ? $model->getQueueableClassName() : get_class($model); } /** * Get the identifiers for all of the entities. * * @return array */ public function getQueueableIds() { if ($this->isEmpty()) { return []; } return $this->first() instanceof QueueableEntity ? $this->map->getQueueableId()->all() : $this->modelKeys(); } /** * Get the relationships of the entities being queued. * * @return array */ public function getQueueableRelations() { if ($this->isEmpty()) { return []; } $relations = $this->map->getQueueableRelations()->all(); if (count($relations) === 0 || $relations === [[]]) { return []; } elseif (count($relations) === 1) { return reset($relations); } else { return array_intersect(...array_values($relations)); } } /** * Get the connection of the entities being queued. * * @return string|null * * @throws \LogicException */ public function getQueueableConnection() { if ($this->isEmpty()) { return; } $connection = $this->first()->getConnectionName(); $this->each(function ($model) use ($connection) { if ($model->getConnectionName() !== $connection) { throw new LogicException('Queueing collections with multiple model connections is not supported.'); } }); return $connection; } /** * Get the Eloquent query builder from the collection. * * @return \Illuminate\Database\Eloquent\Builder * * @throws \LogicException */ public function toQuery() { $model = $this->first(); if (! $model) { throw new LogicException('Unable to create query for empty collection.'); } $class = get_class($model); if ($this->filter(fn ($model) => ! $model instanceof $class)->isNotEmpty()) { throw new LogicException('Unable to create query for collection with mixed types.'); } return $model->newModelQuery()->whereKey($this->modelKeys()); } } Eloquent/Concerns/GuardsAttributes.php000064400000013601147536122140014125 0ustar00 */ protected $fillable = []; /** * The attributes that aren't mass assignable. * * @var array|bool */ protected $guarded = ['*']; /** * Indicates if all mass assignment is enabled. * * @var bool */ protected static $unguarded = false; /** * The actual columns that exist on the database and can be guarded. * * @var array */ protected static $guardableColumns = []; /** * Get the fillable attributes for the model. * * @return array */ public function getFillable() { return $this->fillable; } /** * Set the fillable attributes for the model. * * @param array $fillable * @return $this */ public function fillable(array $fillable) { $this->fillable = $fillable; return $this; } /** * Merge new fillable attributes with existing fillable attributes on the model. * * @param array $fillable * @return $this */ public function mergeFillable(array $fillable) { $this->fillable = array_values(array_unique(array_merge($this->fillable, $fillable))); return $this; } /** * Get the guarded attributes for the model. * * @return array */ public function getGuarded() { return $this->guarded === false ? [] : $this->guarded; } /** * Set the guarded attributes for the model. * * @param array $guarded * @return $this */ public function guard(array $guarded) { $this->guarded = $guarded; return $this; } /** * Merge new guarded attributes with existing guarded attributes on the model. * * @param array $guarded * @return $this */ public function mergeGuarded(array $guarded) { $this->guarded = array_values(array_unique(array_merge($this->guarded, $guarded))); return $this; } /** * Disable all mass assignable restrictions. * * @param bool $state * @return void */ public static function unguard($state = true) { static::$unguarded = $state; } /** * Enable the mass assignment restrictions. * * @return void */ public static function reguard() { static::$unguarded = false; } /** * Determine if the current state is "unguarded". * * @return bool */ public static function isUnguarded() { return static::$unguarded; } /** * Run the given callable while being unguarded. * * @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()) && ! str_contains($key, '.') && ! str_starts_with($key, '_'); } /** * Determine if the given key is guarded. * * @param string $key * @return bool */ public function isGuarded($key) { if (empty($this->getGuarded())) { return false; } return $this->getGuarded() == ['*'] || ! empty(preg_grep('/^'.preg_quote($key, '/').'$/i', $this->getGuarded())) || ! $this->isGuardableColumn($key); } /** * Determine if the given column is a valid, guardable column. * * @param string $key * @return bool */ protected function isGuardableColumn($key) { if (! isset(static::$guardableColumns[get_class($this)])) { $columns = $this->getConnection() ->getSchemaBuilder() ->getColumnListing($this->getTable()); if (empty($columns)) { return true; } static::$guardableColumns[get_class($this)] = $columns; } return in_array($key, static::$guardableColumns[get_class($this)]); } /** * Determine if the model is totally guarded. * * @return bool */ public function totallyGuarded() { return count($this->getFillable()) === 0 && $this->getGuarded() == ['*']; } /** * Get the fillable attributes of a given array. * * @param array $attributes * @return array */ protected function fillableFromArray(array $attributes) { if (count($this->getFillable()) > 0 && ! static::$unguarded) { return array_intersect_key($attributes, array_flip($this->getFillable())); } return $attributes; } } Eloquent/Concerns/HasAttributes.php000064400000201027147536122140013414 0ustar00addDateAttributesToArray( $attributes = $this->getArrayableAttributes() ); $attributes = $this->addMutatedAttributesToArray( $attributes, $mutatedAttributes = $this->getMutatedAttributes() ); // Next we will handle any casts that have been setup for this model and cast // the values to their appropriate type. If the attribute has a mutator we // will not perform the cast on those attributes to avoid any confusion. $attributes = $this->addCastAttributesToArray( $attributes, $mutatedAttributes ); // Here we will grab all of the appended, calculated attributes to this model // as these attributes are not really in the attributes array, but are run // when we need to array or JSON the model for convenience to the coder. foreach ($this->getArrayableAppends() as $key) { $attributes[$key] = $this->mutateAttributeForArray($key, null); } return $attributes; } /** * Add the date attributes to the attributes array. * * @param array $attributes * @return array */ protected function addDateAttributesToArray(array $attributes) { foreach ($this->getDates() as $key) { if (! isset($attributes[$key])) { continue; } $attributes[$key] = $this->serializeDate( $this->asDateTime($attributes[$key]) ); } return $attributes; } /** * Add the mutated attributes to the attributes array. * * @param array $attributes * @param array $mutatedAttributes * @return array */ protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes) { foreach ($mutatedAttributes as $key) { // We want to spin through all the mutated attributes for this model and call // the mutator for the attribute. We cache off every mutated attributes so // we don't have to constantly check on attributes that actually change. if (! array_key_exists($key, $attributes)) { continue; } // Next, we will call the mutator for this attribute so that we can get these // mutated attribute's actual values. After we finish mutating each of the // attributes we will return this final array of the mutated attributes. $attributes[$key] = $this->mutateAttributeForArray( $key, $attributes[$key] ); } return $attributes; } /** * Add the casted attributes to the attributes array. * * @param array $attributes * @param array $mutatedAttributes * @return array */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { foreach ($this->getCasts() as $key => $value) { if (! array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) { continue; } // Here we will cast the attribute. Then, if the cast is a date or datetime cast // then we will serialize the date for the array. This will convert the dates // to strings based on the date format specified for these Eloquent models. $attributes[$key] = $this->castAttribute( $key, $attributes[$key] ); // If the attribute cast was a date or a datetime, we will serialize the date as // a string. This allows the developers to customize how dates are serialized // into an array without affecting how they are persisted into the storage. if (isset($attributes[$key]) && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) { $attributes[$key] = $this->serializeDate($attributes[$key]); } if (isset($attributes[$key]) && ($this->isCustomDateTimeCast($value) || $this->isImmutableCustomDateTimeCast($value))) { $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]); } if ($attributes[$key] instanceof DateTimeInterface && $this->isClassCastable($key)) { $attributes[$key] = $this->serializeDate($attributes[$key]); } if (isset($attributes[$key]) && $this->isClassSerializable($key)) { $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]); } if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) { $attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($attributes[$key]) : null; } if ($attributes[$key] instanceof Arrayable) { $attributes[$key] = $attributes[$key]->toArray(); } } return $attributes; } /** * Get an attribute array of all arrayable attributes. * * @return array */ protected function getArrayableAttributes() { return $this->getArrayableItems($this->getAttributes()); } /** * Get all of the appendable values that are arrayable. * * @return array */ protected function getArrayableAppends() { if (! count($this->appends)) { return []; } return $this->getArrayableItems( array_combine($this->appends, $this->appends) ); } /** * Get the model's relationships in array form. * * @return array */ public function relationsToArray() { $attributes = []; foreach ($this->getArrayableRelations() as $key => $value) { // If the values implement the Arrayable interface we can just call this // toArray method on the instances which will convert both models and // collections to their proper array form and we'll set the values. if ($value instanceof Arrayable) { $relation = $value->toArray(); } // If the value is null, we'll still go ahead and set it in this list of // attributes, since null is used to represent empty relationships if // it has a has one or belongs to type relationships on the models. elseif (is_null($value)) { $relation = $value; } // If the relationships snake-casing is enabled, we will snake case this // key so that the relation attribute is snake cased in this returned // array to the developers, making this consistent with attributes. if (static::$snakeAttributes) { $key = Str::snake($key); } // If the relation value has been set, we will set it on this attributes // list for returning. If it was not arrayable or null, we'll not set // the value on the array because it is some type of invalid value. if (isset($relation) || is_null($value)) { $attributes[$key] = $relation; } unset($relation); } return $attributes; } /** * Get an attribute array of all arrayable relations. * * @return array */ protected function getArrayableRelations() { return $this->getArrayableItems($this->relations); } /** * Get an attribute array of all arrayable values. * * @param array $values * @return array */ protected function getArrayableItems(array $values) { if (count($this->getVisible()) > 0) { $values = array_intersect_key($values, array_flip($this->getVisible())); } if (count($this->getHidden()) > 0) { $values = array_diff_key($values, array_flip($this->getHidden())); } return $values; } /** * Get an attribute from the model. * * @param string $key * @return mixed */ public function getAttribute($key) { if (! $key) { return; } // If the attribute exists in the attribute array or has a "get" mutator we will // get the attribute's value. Otherwise, we will proceed as if the developers // are asking for a relationship's value. This covers both types of values. if (array_key_exists($key, $this->attributes) || array_key_exists($key, $this->casts) || $this->hasGetMutator($key) || $this->hasAttributeMutator($key) || $this->isClassCastable($key)) { return $this->getAttributeValue($key); } // Here we will determine if the model base class itself contains this given key // since we don't want to treat any of those methods as relationships because // they are all intended as helper methods and none of these are relations. if (method_exists(self::class, $key)) { return $this->throwMissingAttributeExceptionIfApplicable($key); } return $this->isRelation($key) || $this->relationLoaded($key) ? $this->getRelationValue($key) : $this->throwMissingAttributeExceptionIfApplicable($key); } /** * Either throw a missing attribute exception or return null depending on Eloquent's configuration. * * @param string $key * @return null * * @throws \Illuminate\Database\Eloquent\MissingAttributeException */ protected function throwMissingAttributeExceptionIfApplicable($key) { if ($this->exists && ! $this->wasRecentlyCreated && static::preventsAccessingMissingAttributes()) { if (isset(static::$missingAttributeViolationCallback)) { return call_user_func(static::$missingAttributeViolationCallback, $this, $key); } throw new MissingAttributeException($this, $key); } return null; } /** * Get a plain attribute (not a relationship). * * @param string $key * @return mixed */ public function getAttributeValue($key) { return $this->transformModelValue($key, $this->getAttributeFromArray($key)); } /** * Get an attribute from the $attributes array. * * @param string $key * @return mixed */ protected function getAttributeFromArray($key) { return $this->getAttributes()[$key] ?? null; } /** * Get a relationship. * * @param string $key * @return mixed */ public function getRelationValue($key) { // If the key already exists in the relationships array, it just means the // relationship has already been loaded, so we'll just return it out of // here because there is no need to query within the relations twice. if ($this->relationLoaded($key)) { return $this->relations[$key]; } if (! $this->isRelation($key)) { return; } if ($this->preventsLazyLoading) { $this->handleLazyLoadingViolation($key); } // If the "attribute" exists as a method on the model, we will just assume // it is a relationship and will load and return results from the query // and hydrate the relationship's value on the "relationships" array. return $this->getRelationshipFromMethod($key); } /** * Determine if the given key is a relationship method on the model. * * @param string $key * @return bool */ public function isRelation($key) { if ($this->hasAttributeMutator($key)) { return false; } return method_exists($this, $key) || $this->relationResolver(static::class, $key); } /** * Handle a lazy loading violation. * * @param string $key * @return mixed */ protected function handleLazyLoadingViolation($key) { if (isset(static::$lazyLoadingViolationCallback)) { return call_user_func(static::$lazyLoadingViolationCallback, $this, $key); } if (! $this->exists || $this->wasRecentlyCreated) { return; } throw new LazyLoadingViolationException($this, $key); } /** * Get a relationship value from a method. * * @param string $method * @return mixed * * @throws \LogicException */ protected function getRelationshipFromMethod($method) { $relation = $this->$method(); if (! $relation instanceof Relation) { if (is_null($relation)) { throw new LogicException(sprintf( '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method )); } throw new LogicException(sprintf( '%s::%s must return a relationship instance.', static::class, $method )); } return tap($relation->getResults(), function ($results) use ($method) { $this->setRelation($method, $results); }); } /** * Determine if a get mutator exists for an attribute. * * @param string $key * @return bool */ public function hasGetMutator($key) { return method_exists($this, 'get'.Str::studly($key).'Attribute'); } /** * Determine if a "Attribute" return type marked mutator exists for an attribute. * * @param string $key * @return bool */ public function hasAttributeMutator($key) { if (isset(static::$attributeMutatorCache[get_class($this)][$key])) { return static::$attributeMutatorCache[get_class($this)][$key]; } if (! method_exists($this, $method = Str::camel($key))) { return static::$attributeMutatorCache[get_class($this)][$key] = false; } $returnType = (new ReflectionMethod($this, $method))->getReturnType(); return static::$attributeMutatorCache[get_class($this)][$key] = $returnType instanceof ReflectionNamedType && $returnType->getName() === Attribute::class; } /** * Determine if a "Attribute" return type marked get mutator exists for an attribute. * * @param string $key * @return bool */ public function hasAttributeGetMutator($key) { if (isset(static::$getAttributeMutatorCache[get_class($this)][$key])) { return static::$getAttributeMutatorCache[get_class($this)][$key]; } if (! $this->hasAttributeMutator($key)) { return static::$getAttributeMutatorCache[get_class($this)][$key] = false; } return static::$getAttributeMutatorCache[get_class($this)][$key] = is_callable($this->{Str::camel($key)}()->get); } /** * Get the value of an attribute using its mutator. * * @param string $key * @param mixed $value * @return mixed */ protected function mutateAttribute($key, $value) { return $this->{'get'.Str::studly($key).'Attribute'}($value); } /** * Get the value of an "Attribute" return type marked attribute using its mutator. * * @param string $key * @param mixed $value * @return mixed */ protected function mutateAttributeMarkedAttribute($key, $value) { if (array_key_exists($key, $this->attributeCastCache)) { return $this->attributeCastCache[$key]; } $attribute = $this->{Str::camel($key)}(); $value = call_user_func($attribute->get ?: function ($value) { return $value; }, $value, $this->attributes); if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) { $this->attributeCastCache[$key] = $value; } else { unset($this->attributeCastCache[$key]); } return $value; } /** * Get the value of an attribute using its mutator for array conversion. * * @param string $key * @param mixed $value * @return mixed */ protected function mutateAttributeForArray($key, $value) { if ($this->isClassCastable($key)) { $value = $this->getClassCastableAttributeValue($key, $value); } elseif (isset(static::$getAttributeMutatorCache[get_class($this)][$key]) && static::$getAttributeMutatorCache[get_class($this)][$key] === true) { $value = $this->mutateAttributeMarkedAttribute($key, $value); $value = $value instanceof DateTimeInterface ? $this->serializeDate($value) : $value; } else { $value = $this->mutateAttribute($key, $value); } return $value instanceof Arrayable ? $value->toArray() : $value; } /** * Merge new casts with existing casts on the model. * * @param array $casts * @return $this */ public function mergeCasts($casts) { $this->casts = array_merge($this->casts, $casts); return $this; } /** * Cast an attribute to a native PHP type. * * @param string $key * @param mixed $value * @return mixed */ protected function castAttribute($key, $value) { $castType = $this->getCastType($key); if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) { return $value; } // If the key is one of the encrypted castable types, we'll first decrypt // the value and update the cast type so we may leverage the following // logic for casting this value to any additionally specified types. if ($this->isEncryptedCastable($key)) { $value = $this->fromEncryptedString($value); $castType = Str::after($castType, 'encrypted:'); } switch ($castType) { case 'int': case 'integer': return (int) $value; case 'real': case 'float': case 'double': return $this->fromFloat($value); case 'decimal': return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]); case 'string': return (string) $value; case 'bool': case 'boolean': return (bool) $value; case 'object': return $this->fromJson($value, true); case 'array': case 'json': return $this->fromJson($value); case 'collection': return new BaseCollection($this->fromJson($value)); case 'date': return $this->asDate($value); case 'datetime': case 'custom_datetime': return $this->asDateTime($value); case 'immutable_date': return $this->asDate($value)->toImmutable(); case 'immutable_custom_datetime': case 'immutable_datetime': return $this->asDateTime($value)->toImmutable(); case 'timestamp': return $this->asTimestamp($value); } if ($this->isEnumCastable($key)) { return $this->getEnumCastableAttributeValue($key, $value); } if ($this->isClassCastable($key)) { return $this->getClassCastableAttributeValue($key, $value); } return $value; } /** * Cast the given attribute using a custom cast class. * * @param string $key * @param mixed $value * @return mixed */ protected function getClassCastableAttributeValue($key, $value) { $caster = $this->resolveCasterClass($key); $objectCachingDisabled = $caster->withoutObjectCaching ?? false; if (isset($this->classCastCache[$key]) && ! $objectCachingDisabled) { return $this->classCastCache[$key]; } else { $value = $caster instanceof CastsInboundAttributes ? $value : $caster->get($this, $key, $value, $this->attributes); if ($caster instanceof CastsInboundAttributes || ! is_object($value) || $objectCachingDisabled) { unset($this->classCastCache[$key]); } else { $this->classCastCache[$key] = $value; } return $value; } } /** * Cast the given attribute to an enum. * * @param string $key * @param mixed $value * @return mixed */ protected function getEnumCastableAttributeValue($key, $value) { if (is_null($value)) { return; } $castType = $this->getCasts()[$key]; if ($value instanceof $castType) { return $value; } return $this->getEnumCaseFromValue($castType, $value); } /** * Get the type of cast for a model attribute. * * @param string $key * @return string */ protected function getCastType($key) { $castType = $this->getCasts()[$key]; if (isset(static::$castTypeCache[$castType])) { return static::$castTypeCache[$castType]; } if ($this->isCustomDateTimeCast($castType)) { $convertedCastType = 'custom_datetime'; } elseif ($this->isImmutableCustomDateTimeCast($castType)) { $convertedCastType = 'immutable_custom_datetime'; } elseif ($this->isDecimalCast($castType)) { $convertedCastType = 'decimal'; } elseif (class_exists($castType)) { $convertedCastType = $castType; } else { $convertedCastType = trim(strtolower($castType)); } return static::$castTypeCache[$castType] = $convertedCastType; } /** * Increment or decrement the given attribute using the custom cast class. * * @param string $method * @param string $key * @param mixed $value * @return mixed */ protected function deviateClassCastableAttribute($method, $key, $value) { return $this->resolveCasterClass($key)->{$method}( $this, $key, $value, $this->attributes ); } /** * Serialize the given attribute using the custom cast class. * * @param string $key * @param mixed $value * @return mixed */ protected function serializeClassCastableAttribute($key, $value) { return $this->resolveCasterClass($key)->serialize( $this, $key, $value, $this->attributes ); } /** * Determine if the cast type is a custom date time cast. * * @param string $cast * @return bool */ protected function isCustomDateTimeCast($cast) { return str_starts_with($cast, 'date:') || str_starts_with($cast, 'datetime:'); } /** * Determine if the cast type is an immutable custom date time cast. * * @param string $cast * @return bool */ protected function isImmutableCustomDateTimeCast($cast) { return str_starts_with($cast, 'immutable_date:') || str_starts_with($cast, 'immutable_datetime:'); } /** * Determine if the cast type is a decimal cast. * * @param string $cast * @return bool */ protected function isDecimalCast($cast) { return str_starts_with($cast, 'decimal:'); } /** * Set a given attribute on the model. * * @param string $key * @param mixed $value * @return mixed */ public function setAttribute($key, $value) { // First we will check for the presence of a mutator for the set operation // which simply lets the developers tweak the attribute as it is set on // this model, such as "json_encoding" a listing of data for storage. if ($this->hasSetMutator($key)) { return $this->setMutatedAttributeValue($key, $value); } elseif ($this->hasAttributeSetMutator($key)) { return $this->setAttributeMarkedMutatedAttributeValue($key, $value); } // If an attribute is listed as a "date", we'll convert it from a DateTime // instance into a form proper for storage on the database tables using // the connection grammar's date format. We will auto set the values. elseif (! is_null($value) && $this->isDateAttribute($key)) { $value = $this->fromDateTime($value); } if ($this->isEnumCastable($key)) { $this->setEnumCastableAttribute($key, $value); return $this; } if ($this->isClassCastable($key)) { $this->setClassCastableAttribute($key, $value); return $this; } if (! is_null($value) && $this->isJsonCastable($key)) { $value = $this->castAttributeAsJson($key, $value); } // If this attribute contains a JSON ->, we'll set the proper value in the // attribute's underlying array. This takes care of properly nesting an // attribute in the array's value in the case of deeply nested items. if (str_contains($key, '->')) { return $this->fillJsonAttribute($key, $value); } if (! is_null($value) && $this->isEncryptedCastable($key)) { $value = $this->castAttributeAsEncryptedString($key, $value); } if (! is_null($value) && $this->hasCast($key, 'hashed')) { $value = $this->castAttributeAsHashedString($key, $value); } $this->attributes[$key] = $value; return $this; } /** * Determine if a set mutator exists for an attribute. * * @param string $key * @return bool */ public function hasSetMutator($key) { return method_exists($this, 'set'.Str::studly($key).'Attribute'); } /** * Determine if an "Attribute" return type marked set mutator exists for an attribute. * * @param string $key * @return bool */ public function hasAttributeSetMutator($key) { $class = get_class($this); if (isset(static::$setAttributeMutatorCache[$class][$key])) { return static::$setAttributeMutatorCache[$class][$key]; } if (! method_exists($this, $method = Str::camel($key))) { return static::$setAttributeMutatorCache[$class][$key] = false; } $returnType = (new ReflectionMethod($this, $method))->getReturnType(); return static::$setAttributeMutatorCache[$class][$key] = $returnType instanceof ReflectionNamedType && $returnType->getName() === Attribute::class && is_callable($this->{$method}()->set); } /** * Set the value of an attribute using its mutator. * * @param string $key * @param mixed $value * @return mixed */ protected function setMutatedAttributeValue($key, $value) { return $this->{'set'.Str::studly($key).'Attribute'}($value); } /** * Set the value of a "Attribute" return type marked attribute using its mutator. * * @param string $key * @param mixed $value * @return mixed */ protected function setAttributeMarkedMutatedAttributeValue($key, $value) { $attribute = $this->{Str::camel($key)}(); $callback = $attribute->set ?: function ($value) use ($key) { $this->attributes[$key] = $value; }; $this->attributes = array_merge( $this->attributes, $this->normalizeCastClassResponse( $key, $callback($value, $this->attributes) ) ); if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) { $this->attributeCastCache[$key] = $value; } else { unset($this->attributeCastCache[$key]); } return $this; } /** * Determine if the given attribute is a date or date castable. * * @param string $key * @return bool */ protected function isDateAttribute($key) { return in_array($key, $this->getDates(), true) || $this->isDateCastable($key); } /** * Set a given JSON attribute on the model. * * @param string $key * @param mixed $value * @return $this */ public function fillJsonAttribute($key, $value) { [$key, $path] = explode('->', $key, 2); $value = $this->asJson($this->getArrayAttributeWithValue( $path, $key, $value )); $this->attributes[$key] = $this->isEncryptedCastable($key) ? $this->castAttributeAsEncryptedString($key, $value) : $value; if ($this->isClassCastable($key)) { unset($this->classCastCache[$key]); } return $this; } /** * Set the value of a class castable attribute. * * @param string $key * @param mixed $value * @return void */ protected function setClassCastableAttribute($key, $value) { $caster = $this->resolveCasterClass($key); $this->attributes = array_replace( $this->attributes, $this->normalizeCastClassResponse($key, $caster->set( $this, $key, $value, $this->attributes )) ); if ($caster instanceof CastsInboundAttributes || ! is_object($value) || ($caster->withoutObjectCaching ?? false)) { unset($this->classCastCache[$key]); } else { $this->classCastCache[$key] = $value; } } /** * Set the value of an enum castable attribute. * * @param string $key * @param \UnitEnum|string|int $value * @return void */ protected function setEnumCastableAttribute($key, $value) { $enumClass = $this->getCasts()[$key]; if (! isset($value)) { $this->attributes[$key] = null; } elseif (is_object($value)) { $this->attributes[$key] = $this->getStorableEnumValue($value); } else { $this->attributes[$key] = $this->getStorableEnumValue( $this->getEnumCaseFromValue($enumClass, $value) ); } } /** * Get an enum case instance from a given class and value. * * @param string $enumClass * @param string|int $value * @return \UnitEnum|\BackedEnum */ protected function getEnumCaseFromValue($enumClass, $value) { return is_subclass_of($enumClass, BackedEnum::class) ? $enumClass::from($value) : constant($enumClass.'::'.$value); } /** * Get the storable value from the given enum. * * @param \UnitEnum|\BackedEnum $value * @return string|int */ protected function getStorableEnumValue($value) { return $value instanceof BackedEnum ? $value->value : $value->name; } /** * Get an array attribute with the given key and value set. * * @param string $path * @param string $key * @param mixed $value * @return $this */ protected function getArrayAttributeWithValue($path, $key, $value) { return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) { Arr::set($array, str_replace('->', '.', $path), $value); }); } /** * Get an array attribute or return an empty array if it is not set. * * @param string $key * @return array */ protected function getArrayAttributeByKey($key) { if (! isset($this->attributes[$key])) { return []; } return $this->fromJson( $this->isEncryptedCastable($key) ? $this->fromEncryptedString($this->attributes[$key]) : $this->attributes[$key] ); } /** * Cast the given attribute to JSON. * * @param string $key * @param mixed $value * @return string */ protected function castAttributeAsJson($key, $value) { $value = $this->asJson($value); if ($value === false) { throw JsonEncodingException::forAttribute( $this, $key, json_last_error_msg() ); } return $value; } /** * Encode the given value as JSON. * * @param mixed $value * @return string */ protected function asJson($value) { return Json::encode($value); } /** * Decode the given JSON back into an array or object. * * @param string $value * @param bool $asObject * @return mixed */ public function fromJson($value, $asObject = false) { return Json::decode($value ?? '', ! $asObject); } /** * Decrypt the given encrypted string. * * @param string $value * @return mixed */ public function fromEncryptedString($value) { return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false); } /** * Cast the given attribute to an encrypted string. * * @param string $key * @param mixed $value * @return string */ protected function castAttributeAsEncryptedString($key, $value) { return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false); } /** * Set the encrypter instance that will be used to encrypt attributes. * * @param \Illuminate\Contracts\Encryption\Encrypter|null $encrypter * @return void */ public static function encryptUsing($encrypter) { static::$encrypter = $encrypter; } /** * Cast the given attribute to a hashed string. * * @param string $key * @param mixed $value * @return string */ protected function castAttributeAsHashedString($key, $value) { return $value !== null && ! Hash::isHashed($value) ? Hash::make($value) : $value; } /** * Decode the given float. * * @param mixed $value * @return mixed */ public function fromFloat($value) { return match ((string) $value) { 'Infinity' => INF, '-Infinity' => -INF, 'NaN' => NAN, default => (float) $value, }; } /** * Return a decimal as string. * * @param float|string $value * @param int $decimals * @return string */ protected function asDecimal($value, $decimals) { try { return (string) BigDecimal::of($value)->toScale($decimals, RoundingMode::HALF_UP); } catch (BrickMathException $e) { throw new MathException('Unable to cast value to a decimal.', previous: $e); } } /** * Return a timestamp as DateTime object with time set to 00:00:00. * * @param mixed $value * @return \Illuminate\Support\Carbon */ protected function asDate($value) { return $this->asDateTime($value)->startOfDay(); } /** * Return a timestamp as DateTime object. * * @param mixed $value * @return \Illuminate\Support\Carbon */ protected function asDateTime($value) { // If this value is already a Carbon instance, we shall just return it as is. // This prevents us having to re-instantiate a Carbon instance when we know // it already is one, which wouldn't be fulfilled by the DateTime check. if ($value instanceof CarbonInterface) { return Date::instance($value); } // If the value is already a DateTime instance, we will just skip the rest of // these checks since they will be a waste of time, and hinder performance // when checking the field. We will just return the DateTime right away. if ($value instanceof DateTimeInterface) { return Date::parse( $value->format('Y-m-d H:i:s.u'), $value->getTimezone() ); } // If this value is an integer, we will assume it is a UNIX timestamp's value // and format a Carbon object from this timestamp. This allows flexibility // when defining your date fields as they might be UNIX timestamps here. if (is_numeric($value)) { return Date::createFromTimestamp($value); } // If the value is in simply year, month, day format, we will instantiate the // Carbon instances from that format. Again, this provides for simple date // fields on the database, while still supporting Carbonized conversion. if ($this->isStandardDateFormat($value)) { return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay()); } $format = $this->getDateFormat(); // Finally, we will just assume this date is in the format used by default on // the database connection and use that format to create the Carbon object // that is returned back out to the developers after we convert it here. try { $date = Date::createFromFormat($format, $value); } catch (InvalidArgumentException) { $date = false; } return $date ?: Date::parse($value); } /** * Determine if the given value is a standard date format. * * @param string $value * @return bool */ protected function isStandardDateFormat($value) { return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value); } /** * Convert a DateTime to a storable string. * * @param mixed $value * @return string|null */ public function fromDateTime($value) { return empty($value) ? $value : $this->asDateTime($value)->format( $this->getDateFormat() ); } /** * Return a timestamp as unix timestamp. * * @param mixed $value * @return int */ protected function asTimestamp($value) { return $this->asDateTime($value)->getTimestamp(); } /** * Prepare a date for array / JSON serialization. * * @param \DateTimeInterface $date * @return string */ protected function serializeDate(DateTimeInterface $date) { return $date instanceof DateTimeImmutable ? CarbonImmutable::instance($date)->toJSON() : Carbon::instance($date)->toJSON(); } /** * Get the attributes that should be converted to dates. * * @return array */ public function getDates() { return $this->usesTimestamps() ? [ $this->getCreatedAtColumn(), $this->getUpdatedAtColumn(), ] : []; } /** * Get the format for database stored dates. * * @return string */ public function getDateFormat() { return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat(); } /** * Set the date format used by the model. * * @param string $format * @return $this */ public function setDateFormat($format) { $this->dateFormat = $format; return $this; } /** * Determine whether an attribute should be cast to a native type. * * @param string $key * @param array|string|null $types * @return bool */ public function hasCast($key, $types = null) { if (array_key_exists($key, $this->getCasts())) { return $types ? in_array($this->getCastType($key), (array) $types, true) : true; } return false; } /** * Get the casts array. * * @return array */ public function getCasts() { if ($this->getIncrementing()) { return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts); } return $this->casts; } /** * Determine whether a value is Date / DateTime castable for inbound manipulation. * * @param string $key * @return bool */ protected function isDateCastable($key) { return $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']); } /** * Determine whether a value is Date / DateTime custom-castable for inbound manipulation. * * @param string $key * @return bool */ protected function isDateCastableWithCustomFormat($key) { return $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']); } /** * Determine whether a value is JSON castable for inbound manipulation. * * @param string $key * @return bool */ protected function isJsonCastable($key) { return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); } /** * Determine whether a value is an encrypted castable for inbound manipulation. * * @param string $key * @return bool */ protected function isEncryptedCastable($key) { return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); } /** * Determine if the given key is cast using a custom class. * * @param string $key * @return bool * * @throws \Illuminate\Database\Eloquent\InvalidCastException */ protected function isClassCastable($key) { $casts = $this->getCasts(); if (! array_key_exists($key, $casts)) { return false; } $castType = $this->parseCasterClass($casts[$key]); if (in_array($castType, static::$primitiveCastTypes)) { return false; } if (class_exists($castType)) { return true; } throw new InvalidCastException($this->getModel(), $key, $castType); } /** * Determine if the given key is cast using an enum. * * @param string $key * @return bool */ protected function isEnumCastable($key) { $casts = $this->getCasts(); if (! array_key_exists($key, $casts)) { return false; } $castType = $casts[$key]; if (in_array($castType, static::$primitiveCastTypes)) { return false; } return enum_exists($castType); } /** * Determine if the key is deviable using a custom class. * * @param string $key * @return bool * * @throws \Illuminate\Database\Eloquent\InvalidCastException */ protected function isClassDeviable($key) { if (! $this->isClassCastable($key)) { return false; } $castType = $this->resolveCasterClass($key); return method_exists($castType::class, 'increment') && method_exists($castType::class, 'decrement'); } /** * Determine if the key is serializable using a custom class. * * @param string $key * @return bool * * @throws \Illuminate\Database\Eloquent\InvalidCastException */ protected function isClassSerializable($key) { return ! $this->isEnumCastable($key) && $this->isClassCastable($key) && method_exists($this->resolveCasterClass($key), 'serialize'); } /** * Resolve the custom caster class for a given key. * * @param string $key * @return mixed */ protected function resolveCasterClass($key) { $castType = $this->getCasts()[$key]; $arguments = []; if (is_string($castType) && str_contains($castType, ':')) { $segments = explode(':', $castType, 2); $castType = $segments[0]; $arguments = explode(',', $segments[1]); } if (is_subclass_of($castType, Castable::class)) { $castType = $castType::castUsing($arguments); } if (is_object($castType)) { return $castType; } return new $castType(...$arguments); } /** * Parse the given caster class, removing any arguments. * * @param string $class * @return string */ protected function parseCasterClass($class) { return ! str_contains($class, ':') ? $class : explode(':', $class, 2)[0]; } /** * Merge the cast class and attribute cast attributes back into the model. * * @return void */ protected function mergeAttributesFromCachedCasts() { $this->mergeAttributesFromClassCasts(); $this->mergeAttributesFromAttributeCasts(); } /** * Merge the cast class attributes back into the model. * * @return void */ protected function mergeAttributesFromClassCasts() { foreach ($this->classCastCache as $key => $value) { $caster = $this->resolveCasterClass($key); $this->attributes = array_merge( $this->attributes, $caster instanceof CastsInboundAttributes ? [$key => $value] : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes)) ); } } /** * Merge the cast class attributes back into the model. * * @return void */ protected function mergeAttributesFromAttributeCasts() { foreach ($this->attributeCastCache as $key => $value) { $attribute = $this->{Str::camel($key)}(); if ($attribute->get && ! $attribute->set) { continue; } $callback = $attribute->set ?: function ($value) use ($key) { $this->attributes[$key] = $value; }; $this->attributes = array_merge( $this->attributes, $this->normalizeCastClassResponse( $key, $callback($value, $this->attributes) ) ); } } /** * Normalize the response from a custom class caster. * * @param string $key * @param mixed $value * @return array */ protected function normalizeCastClassResponse($key, $value) { return is_array($value) ? $value : [$key => $value]; } /** * Get all of the current attributes on the model. * * @return array */ public function getAttributes() { $this->mergeAttributesFromCachedCasts(); return $this->attributes; } /** * Get all of the current attributes on the model for an insert operation. * * @return array */ protected function getAttributesForInsert() { return $this->getAttributes(); } /** * Set the array of model attributes. No checking is done. * * @param array $attributes * @param bool $sync * @return $this */ public function setRawAttributes(array $attributes, $sync = false) { $this->attributes = $attributes; if ($sync) { $this->syncOriginal(); } $this->classCastCache = []; $this->attributeCastCache = []; return $this; } /** * Get the model's original attribute values. * * @param string|null $key * @param mixed $default * @return mixed|array */ public function getOriginal($key = null, $default = null) { return (new static)->setRawAttributes( $this->original, $sync = true )->getOriginalWithoutRewindingModel($key, $default); } /** * Get the model's original attribute values. * * @param string|null $key * @param mixed $default * @return mixed|array */ protected function getOriginalWithoutRewindingModel($key = null, $default = null) { if ($key) { return $this->transformModelValue( $key, Arr::get($this->original, $key, $default) ); } return collect($this->original)->mapWithKeys(function ($value, $key) { return [$key => $this->transformModelValue($key, $value)]; })->all(); } /** * Get the model's raw original attribute values. * * @param string|null $key * @param mixed $default * @return mixed|array */ public function getRawOriginal($key = null, $default = null) { return Arr::get($this->original, $key, $default); } /** * Get a subset of the model's attributes. * * @param array|mixed $attributes * @return array */ public function only($attributes) { $results = []; foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) { $results[$attribute] = $this->getAttribute($attribute); } return $results; } /** * Sync the original attributes with the current. * * @return $this */ public function syncOriginal() { $this->original = $this->getAttributes(); return $this; } /** * Sync a single original attribute with its current value. * * @param string $attribute * @return $this */ public function syncOriginalAttribute($attribute) { return $this->syncOriginalAttributes($attribute); } /** * Sync multiple original attribute with their current values. * * @param array|string $attributes * @return $this */ public function syncOriginalAttributes($attributes) { $attributes = is_array($attributes) ? $attributes : func_get_args(); $modelAttributes = $this->getAttributes(); foreach ($attributes as $attribute) { $this->original[$attribute] = $modelAttributes[$attribute]; } return $this; } /** * Sync the changed attributes. * * @return $this */ public function syncChanges() { $this->changes = $this->getDirty(); return $this; } /** * Determine if the model or any of the given attribute(s) have been modified. * * @param array|string|null $attributes * @return bool */ public function isDirty($attributes = null) { return $this->hasChanges( $this->getDirty(), is_array($attributes) ? $attributes : func_get_args() ); } /** * Determine if the model or all the given attribute(s) have remained the same. * * @param array|string|null $attributes * @return bool */ public function isClean($attributes = null) { return ! $this->isDirty(...func_get_args()); } /** * Discard attribute changes and reset the attributes to their original state. * * @return $this */ public function discardChanges() { [$this->attributes, $this->changes] = [$this->original, []]; return $this; } /** * Determine if the model or any of the given attribute(s) were changed when the model was last saved. * * @param array|string|null $attributes * @return bool */ public function wasChanged($attributes = null) { return $this->hasChanges( $this->getChanges(), is_array($attributes) ? $attributes : func_get_args() ); } /** * Determine if any of the given attributes were changed when the model was last saved. * * @param array $changes * @param array|string|null $attributes * @return bool */ protected function hasChanges($changes, $attributes = null) { // If no specific attributes were provided, we will just see if the dirty array // already contains any attributes. If it does we will just return that this // count is greater than zero. Else, we need to check specific attributes. if (empty($attributes)) { return count($changes) > 0; } // Here we will spin through every attribute and see if this is in the array of // dirty attributes. If it is, we will return true and if we make it through // all of the attributes for the entire array we will return false at end. foreach (Arr::wrap($attributes) as $attribute) { if (array_key_exists($attribute, $changes)) { return true; } } return false; } /** * Get the attributes that have been changed since the last sync. * * @return array */ public function getDirty() { $dirty = []; foreach ($this->getAttributes() as $key => $value) { if (! $this->originalIsEquivalent($key)) { $dirty[$key] = $value; } } return $dirty; } /** * Get the attributes that were changed when the model was last saved. * * @return array */ public function getChanges() { return $this->changes; } /** * Determine if the new and old values for a given key are equivalent. * * @param string $key * @return bool */ public function originalIsEquivalent($key) { if (! array_key_exists($key, $this->original)) { return false; } $attribute = Arr::get($this->attributes, $key); $original = Arr::get($this->original, $key); if ($attribute === $original) { return true; } elseif (is_null($attribute)) { return false; } elseif ($this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key)) { return $this->fromDateTime($attribute) === $this->fromDateTime($original); } elseif ($this->hasCast($key, ['object', 'collection'])) { return $this->fromJson($attribute) === $this->fromJson($original); } elseif ($this->hasCast($key, ['real', 'float', 'double'])) { if ($original === null) { return false; } return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; } elseif ($this->hasCast($key, static::$primitiveCastTypes)) { return $this->castAttribute($key, $attribute) === $this->castAttribute($key, $original); } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) { return $this->fromJson($attribute) === $this->fromJson($original); } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsEnumArrayObject::class, AsEnumCollection::class])) { return $this->fromJson($attribute) === $this->fromJson($original); } elseif ($this->isClassCastable($key) && $original !== null && Str::startsWith($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) { return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original); } return is_numeric($attribute) && is_numeric($original) && strcmp((string) $attribute, (string) $original) === 0; } /** * Transform a raw model value using mutators, casts, etc. * * @param string $key * @param mixed $value * @return mixed */ protected function transformModelValue($key, $value) { // If the attribute has a get mutator, we will call that then return what // it returns as the value, which is useful for transforming values on // retrieval from the model to a form that is more useful for usage. if ($this->hasGetMutator($key)) { return $this->mutateAttribute($key, $value); } elseif ($this->hasAttributeGetMutator($key)) { return $this->mutateAttributeMarkedAttribute($key, $value); } // If the attribute exists within the cast array, we will convert it to // an appropriate native PHP type dependent upon the associated value // given with the key in the pair. Dayle made this comment line up. if ($this->hasCast($key)) { return $this->castAttribute($key, $value); } // If the attribute is listed as a date, we will convert it to a DateTime // instance on retrieval, which makes it quite convenient to work with // date fields without having to create a mutator for each property. if ($value !== null && \in_array($key, $this->getDates(), false)) { return $this->asDateTime($value); } return $value; } /** * Append attributes to query when building a query. * * @param array|string $attributes * @return $this */ public function append($attributes) { $this->appends = array_values(array_unique( array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes) )); return $this; } /** * Get the accessors that are being appended to model arrays. * * @return array */ public function getAppends() { return $this->appends; } /** * Set the accessors to append to model arrays. * * @param array $appends * @return $this */ public function setAppends(array $appends) { $this->appends = $appends; return $this; } /** * Return whether the accessor attribute has been appended. * * @param string $attribute * @return bool */ public function hasAppended($attribute) { return in_array($attribute, $this->appends); } /** * Get the mutated attributes for a given instance. * * @return array */ public function getMutatedAttributes() { if (! isset(static::$mutatorCache[static::class])) { static::cacheMutatedAttributes($this); } return static::$mutatorCache[static::class]; } /** * Extract and cache all the mutated attributes of a class. * * @param object|string $classOrInstance * @return void */ public static function cacheMutatedAttributes($classOrInstance) { $reflection = new ReflectionClass($classOrInstance); $class = $reflection->getName(); static::$getAttributeMutatorCache[$class] = collect($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($classOrInstance)) ->mapWithKeys(function ($match) { return [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true]; })->all(); static::$mutatorCache[$class] = collect(static::getMutatorMethods($class)) ->merge($attributeMutatorMethods) ->map(function ($match) { return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match); })->all(); } /** * Get all of the attribute mutator methods. * * @param mixed $class * @return array */ protected static function getMutatorMethods($class) { preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); return $matches[1]; } /** * Get all of the "Attribute" return typed attribute mutator methods. * * @param mixed $class * @return array */ protected static function getAttributeMarkedMutatorMethods($class) { $instance = is_object($class) ? $class : new $class; return collect((new ReflectionClass($instance))->getMethods())->filter(function ($method) use ($instance) { $returnType = $method->getReturnType(); if ($returnType instanceof ReflectionNamedType && $returnType->getName() === Attribute::class) { if (is_callable($method->invoke($instance)->get)) { return true; } } return false; })->map->name->values()->all(); } } Eloquent/Concerns/HasEvents.php000064400000024731147536122140012537 0ustar00registerObserver($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', 'forceDeleting', '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 \Illuminate\Events\QueuedClosure|\Closure|string|array $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 \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function retrieved($callback) { static::registerModelEvent('retrieved', $callback); } /** * Register a saving model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function saving($callback) { static::registerModelEvent('saving', $callback); } /** * Register a saved model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function saved($callback) { static::registerModelEvent('saved', $callback); } /** * Register an updating model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function updating($callback) { static::registerModelEvent('updating', $callback); } /** * Register an updated model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function updated($callback) { static::registerModelEvent('updated', $callback); } /** * Register a creating model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function creating($callback) { static::registerModelEvent('creating', $callback); } /** * Register a created model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function created($callback) { static::registerModelEvent('created', $callback); } /** * Register a replicating model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function replicating($callback) { static::registerModelEvent('replicating', $callback); } /** * Register a deleting model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function deleting($callback) { static::registerModelEvent('deleting', $callback); } /** * Register a deleted model event with the dispatcher. * * @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback * @return void */ public static function deleted($callback) { static::registerModelEvent('deleted', $callback); } /** * Remove all the event listeners for the model. * * @return void */ public static function flushEventListeners() { if (! isset(static::$dispatcher)) { return; } $instance = new static; foreach ($instance->getObservableEvents() as $event) { static::$dispatcher->forget("eloquent.{$event}: ".static::class); } foreach (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); } } } } Eloquent/Concerns/HasGlobalScopes.php000064400000005016147536122140013643 0ustar00relationResolver($parent, $key); } return null; } /** * Define a dynamic relation resolver. * * @param string $name * @param \Closure $callback * @return void */ public static function resolveRelationUsing($name, Closure $callback) { static::$relationResolvers = array_replace_recursive( static::$relationResolvers, [static::class => [$name => $callback]] ); } /** * Define a one-to-one relationship. * * @param string $related * @param string|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 = $this->newRelatedThroughInstance($through); $firstKey = $firstKey ?: $this->getForeignKey(); $secondKey = $secondKey ?: $through->getForeignKey(); return $this->newHasOneThrough( $this->newRelatedInstance($related)->newQuery(), $this, $through, $firstKey, $secondKey, $localKey ?: $this->getKeyName(), $secondLocalKey ?: $through->getKeyName() ); } /** * Instantiate a new HasOneThrough relationship. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Model $farParent * @param \Illuminate\Database\Eloquent\Model $throughParent * @param string $firstKey * @param string $secondKey * @param string $localKey * @param string $secondLocalKey * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough */ protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey); } /** * 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 return the relationship instance which will // actually be responsible for retrieving and hydrating every relation. $ownerKey = $ownerKey ?: $instance->getKeyName(); return $this->newBelongsTo( $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation ); } /** * Instantiate a new BelongsTo relationship. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Model $child * @param string $foreignKey * @param string $ownerKey * @param string $relation * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) { return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation); } /** * Define a polymorphic, inverse one-to-one or many relationship. * * @param string|null $name * @param string|null $type * @param string|null $id * @param string|null $ownerKey * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null) { // If no name is provided, we will use the backtrace to get the function name // since that is most likely the name of the polymorphic interface. We can // use that to get both the class and foreign key that will be utilized. $name = $name ?: $this->guessBelongsToRelation(); [$type, $id] = $this->getMorphs( Str::snake($name), $type, $id ); // If the type value is null it is probably safe to assume we're eager loading // the relationship. In this case we'll just pass in a dummy query where we // need to remove any eager loads that may already be defined on a model. return is_null($class = $this->getAttributeFromArray($type)) || $class === '' ? $this->morphEagerTo($name, $type, $id, $ownerKey) : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey); } /** * Define a polymorphic, inverse one-to-one or many relationship. * * @param string $name * @param string $type * @param string $id * @param string $ownerKey * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ protected function morphEagerTo($name, $type, $id, $ownerKey) { return $this->newMorphTo( $this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name ); } /** * Define a polymorphic, inverse one-to-one or many relationship. * * @param string $target * @param string $name * @param string $type * @param string $id * @param string $ownerKey * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ protected function morphInstanceTo($target, $name, $type, $id, $ownerKey) { $instance = $this->newRelatedInstance( static::getActualClassNameForMorph($target) ); return $this->newMorphTo( $instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name ); } /** * Instantiate a new MorphTo relationship. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Model $parent * @param string $foreignKey * @param string $ownerKey * @param string $type * @param string $relation * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) { return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation); } /** * Retrieve the actual class name for a given morph class. * * @param string $class * @return string */ public static function getActualClassNameForMorph($class) { return Arr::get(Relation::morphMap() ?: [], $class, $class); } /** * Guess the "belongs to" relationship name. * * @return string */ protected function guessBelongsToRelation() { [$one, $two, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); return $caller['function']; } /** * Create a pending has-many-through or has-one-through relationship. * * @param string|\Illuminate\Database\Eloquent\Relations\HasMany|\Illuminate\Database\Eloquent\Relations\HasOne $relationship * @return \Illuminate\Database\Eloquent\PendingHasThroughRelationship */ public function through($relationship) { if (is_string($relationship)) { $relationship = $this->{$relationship}(); } return new PendingHasThroughRelationship($this, $relationship); } /** * Define a one-to-many relationship. * * @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 = $this->newRelatedThroughInstance($through); $firstKey = $firstKey ?: $this->getForeignKey(); $secondKey = $secondKey ?: $through->getForeignKey(); return $this->newHasManyThrough( $this->newRelatedInstance($related)->newQuery(), $this, $through, $firstKey, $secondKey, $localKey ?: $this->getKeyName(), $secondLocalKey ?: $through->getKeyName() ); } /** * Instantiate a new HasManyThrough relationship. * * @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|class-string<\Illuminate\Database\Eloquent\Model>|null $table * @param string|null $foreignPivotKey * @param string|null $relatedPivotKey * @param string|null $parentKey * @param string|null $relatedKey * @param string|null $relation * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null) { // If no relationship name was passed, we will pull backtraces to get the // name of the calling function. We will use that function name as the // title of this relation since that is a great convention to apply. if (is_null($relation)) { $relation = $this->guessBelongsToManyRelation(); } // First, we'll need to determine the foreign key and "other key" for the // relationship. Once we have determined the keys we'll make the query // instances as well as the relationship instances we need for this. $instance = $this->newRelatedInstance($related); $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); // If no table name was provided, we can guess it by concatenating the two // models using underscores in alphabetical order. The two model names // are transformed to snake case from their default CamelCase also. if (is_null($table)) { $table = $this->joiningTable($related, $instance); } return $this->newBelongsToMany( $instance->newQuery(), $this, $table, $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), $relatedKey ?: $instance->getKeyName(), $relation ); } /** * Instantiate a new BelongsToMany relationship. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Model $parent * @param string|class-string<\Illuminate\Database\Eloquent\Model> $table * @param string $foreignPivotKey * @param string $relatedPivotKey * @param string $parentKey * @param string $relatedKey * @param string|null $relationName * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName = null) { return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName); } /** * Define a polymorphic many-to-many relationship. * * @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 string|null $relation * @param bool $inverse * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function morphToMany($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null, $inverse = false) { $relation = $relation ?: $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 the related model and // the relationship instances for this relation. This relation will set // appropriate query constraints then entirely manage the hydrations. if (! $table) { $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); $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(), $relation, $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 * @param string|null $relation * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = 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, $relation, true ); } /** * Get the relationship name of the belongsToMany relationship. * * @return string|null */ protected function guessBelongsToManyRelation() { $caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) { return ! in_array( $trace['function'], array_merge(static::$manyMethods, ['guessBelongsToManyRelation']) ); }); return ! is_null($caller) ? $caller['function'] : null; } /** * Get the joining table name for a many-to-many relation. * * @param string $related * @param \Illuminate\Database\Eloquent\Model|null $instance * @return string */ public function joiningTable($related, $instance = null) { // The joining table name, by convention, is simply the snake cased models // sorted alphabetically and concatenated with an underscore, so we can // just sort the models and join them together to get the table name. $segments = [ $instance ? $instance->joiningTableSegment() : Str::snake(class_basename($related)), $this->joiningTableSegment(), ]; // Now that we have the model names in an array we can just sort them and // use the implode function to join them together with an underscores, // which is typically used by convention within the database system. sort($segments); return strtolower(implode('_', $segments)); } /** * Get this model's half of the intermediate table name for belongsToMany relationships. * * @return string */ public function joiningTableSegment() { return Str::snake(class_basename($this)); } /** * Determine if the model touches a given relation. * * @param string $relation * @return bool */ public function touches($relation) { return in_array($relation, $this->getTouchedRelations()); } /** * Touch the owning relations of the model. * * @return void */ public function touchOwners() { foreach ($this->getTouchedRelations() as $relation) { $this->$relation()->touch(); if ($this->$relation instanceof self) { $this->$relation->fireModelEvent('saved', false); $this->$relation->touchOwners(); } elseif ($this->$relation instanceof Collection) { $this->$relation->each->touchOwners(); } } } /** * Get the polymorphic relationship columns. * * @param string $name * @param string $type * @param string $id * @return array */ protected function getMorphs($name, $type, $id) { return [$type ?: $name.'_type', $id ?: $name.'_id']; } /** * Get the class name for polymorphic relations. * * @return string */ public function getMorphClass() { $morphMap = Relation::morphMap(); if (! empty($morphMap) && in_array(static::class, $morphMap)) { return array_search(static::class, $morphMap, true); } if (static::class === Pivot::class) { return static::class; } if (Relation::requiresMorphMap()) { throw new ClassMorphViolationException($this); } return static::class; } /** * Create a new model instance for a related model. * * @param string $class * @return mixed */ protected function newRelatedInstance($class) { return tap(new $class, function ($instance) { if (! $instance->getConnectionName()) { $instance->setConnection($this->connection); } }); } /** * Create a new model instance for a related "through" model. * * @param string $class * @return mixed */ protected function newRelatedThroughInstance($class) { return new $class; } /** * Get all the loaded relations for the instance. * * @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; } } Eloquent/Concerns/HasTimestamps.php000064400000011736147536122140013422 0ustar00$attribute = $this->freshTimestamp(); return $this->save(); } if (! $this->usesTimestamps()) { return false; } $this->updateTimestamps(); return $this->save(); } /** * Update the model's update timestamp without raising any events. * * @param string|null $attribute * @return bool */ public function touchQuietly($attribute = null) { return static::withoutEvents(fn () => $this->touch($attribute)); } /** * Update the creation and update timestamps. * * @return $this */ public function updateTimestamps() { $time = $this->freshTimestamp(); $updatedAtColumn = $this->getUpdatedAtColumn(); if (! is_null($updatedAtColumn) && ! $this->isDirty($updatedAtColumn)) { $this->setUpdatedAt($time); } $createdAtColumn = $this->getCreatedAtColumn(); if (! $this->exists && ! is_null($createdAtColumn) && ! $this->isDirty($createdAtColumn)) { $this->setCreatedAt($time); } return $this; } /** * 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 && ! static::isIgnoringTimestamps($this::class); } /** * Get the name of the "created at" column. * * @return string|null */ public function getCreatedAtColumn() { return static::CREATED_AT; } /** * Get the name of the "updated at" column. * * @return string|null */ public function getUpdatedAtColumn() { return static::UPDATED_AT; } /** * Get the fully qualified "created at" column. * * @return string|null */ public function getQualifiedCreatedAtColumn() { return $this->qualifyColumn($this->getCreatedAtColumn()); } /** * Get the fully qualified "updated at" column. * * @return string|null */ public function getQualifiedUpdatedAtColumn() { return $this->qualifyColumn($this->getUpdatedAtColumn()); } /** * Disable timestamps for the current class during the given callback scope. * * @param callable $callback * @return mixed */ public static function withoutTimestamps(callable $callback) { return static::withoutTimestampsOn([static::class], $callback); } /** * Disable timestamps for the given model classes during the given callback scope. * * @param array $models * @param callable $callback * @return mixed */ public static function withoutTimestampsOn($models, $callback) { static::$ignoreTimestampsOn = array_values(array_merge(static::$ignoreTimestampsOn, $models)); try { return $callback(); } finally { static::$ignoreTimestampsOn = array_values(array_diff(static::$ignoreTimestampsOn, $models)); } } /** * Determine if the given model is ignoring timestamps / touches. * * @param string|null $class * @return bool */ public static function isIgnoringTimestamps($class = null) { $class ??= static::class; foreach (static::$ignoreTimestampsOn as $ignoredClass) { if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) { return true; } } return false; } } Eloquent/Concerns/HasUlids.php000064400000004217147536122140012350 0ustar00usesUniqueIds = true; } /** * Get the columns that should receive a unique identifier. * * @return array */ public function uniqueIds() { return [$this->getKeyName()]; } /** * Generate a new ULID for the model. * * @return string */ public function newUniqueId() { return strtolower((string) Str::ulid()); } /** * Retrieve the model for a bound value. * * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Relations\Relation * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function resolveRouteBindingQuery($query, $value, $field = null) { if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUlid($value)) { throw (new ModelNotFoundException)->setModel(get_class($this), $value); } if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUlid($value)) { throw (new ModelNotFoundException)->setModel(get_class($this), $value); } return parent::resolveRouteBindingQuery($query, $value, $field); } /** * Get the auto-incrementing key type. * * @return string */ public function getKeyType() { if (in_array($this->getKeyName(), $this->uniqueIds())) { return 'string'; } return $this->keyType; } /** * Get the value indicating whether the IDs are incrementing. * * @return bool */ public function getIncrementing() { if (in_array($this->getKeyName(), $this->uniqueIds())) { return false; } return $this->incrementing; } } Eloquent/Concerns/HasUniqueIds.php000064400000001764147536122140013202 0ustar00usesUniqueIds; } /** * Generate unique keys for the model. * * @return void */ public function setUniqueIds() { foreach ($this->uniqueIds() as $column) { if (empty($this->{$column})) { $this->{$column} = $this->newUniqueId(); } } } /** * Generate a new key for the model. * * @return string */ public function newUniqueId() { return null; } /** * Get the columns that should receive a unique identifier. * * @return array */ public function uniqueIds() { return []; } } Eloquent/Concerns/HasUuids.php000064400000004212147536122140012354 0ustar00usesUniqueIds = true; } /** * Get the columns that should receive a unique identifier. * * @return array */ public function uniqueIds() { return [$this->getKeyName()]; } /** * Generate a new UUID for the model. * * @return string */ public function newUniqueId() { return (string) Str::orderedUuid(); } /** * Retrieve the model for a bound value. * * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Relations\Relation * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function resolveRouteBindingQuery($query, $value, $field = null) { if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUuid($value)) { throw (new ModelNotFoundException)->setModel(get_class($this), $value); } if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUuid($value)) { throw (new ModelNotFoundException)->setModel(get_class($this), $value); } return parent::resolveRouteBindingQuery($query, $value, $field); } /** * Get the auto-incrementing key type. * * @return string */ public function getKeyType() { if (in_array($this->getKeyName(), $this->uniqueIds())) { return 'string'; } return $this->keyType; } /** * Get the value indicating whether the IDs are incrementing. * * @return bool */ public function getIncrementing() { if (in_array($this->getKeyName(), $this->uniqueIds())) { return false; } return $this->incrementing; } } Eloquent/Concerns/HidesAttributes.php000064400000005535147536122140013743 0ustar00 */ 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_values(array_unique(array_merge($this->visible, $attributes))); } return $this; } /** * Make the given, typically hidden, attributes visible if the given truth test passes. * * @param bool|\Closure $condition * @param array|string|null $attributes * @return $this */ public function makeVisibleIf($condition, $attributes) { return value($condition, $this) ? $this->makeVisible($attributes) : $this; } /** * Make the given, typically visible, attributes hidden. * * @param array|string|null $attributes * @return $this */ public function makeHidden($attributes) { $this->hidden = array_values(array_unique(array_merge( $this->hidden, is_array($attributes) ? $attributes : func_get_args() ))); return $this; } /** * Make the given, typically visible, attributes hidden if the given truth test passes. * * @param bool|\Closure $condition * @param array|string|null $attributes * @return $this */ public function makeHiddenIf($condition, $attributes) { return value($condition, $this) ? $this->makeHidden($attributes) : $this; } } Eloquent/Concerns/QueriesRelationships.php000064400000076557147536122140015036 0ustar00=', $count = 1, $boolean = 'and', Closure $callback = null) { if (is_string($relation)) { if (str_contains($relation, '.')) { return $this->hasNested($relation, $operator, $count, $boolean, $callback); } $relation = $this->getRelationWithoutConstraints($relation); } if ($relation instanceof MorphTo) { return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback); } // If we only need to check for the existence of the relation, then we can optimize // the subquery to only run a "where exists" clause instead of this full "count" // clause. This will make these queries run much faster compared with a count. $method = $this->canUseExistsForExistenceCheck($operator, $count) ? 'getRelationExistenceQuery' : 'getRelationExistenceCountQuery'; $hasQuery = $relation->{$method}( $relation->getRelated()->newQueryWithoutRelationships(), $this ); // Next we will call any given callback as an "anonymous" scope so they can get the // proper logical grouping of the where clauses if needed by this Eloquent query // builder. Then, we will be ready to finalize and return this query instance. if ($callback) { $hasQuery->callScope($callback); } return $this->addHasWhere( $hasQuery, $relation, $operator, $count, $boolean ); } /** * Add nested relationship count / exists conditions to the query. * * Sets up recursive call to whereHas until we finish the nested relation. * * @param string $relations * @param string $operator * @param int $count * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) { $relations = explode('.', $relations); $doesntHave = $operator === '<' && $count === 1; if ($doesntHave) { $operator = '>='; $count = 1; } $closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) { // In order to nest "has", we need to add count relation constraints on the // callback Closure. We'll do this by simply passing the Closure its own // reference to itself so it calls itself recursively on each segment. count($relations) > 1 ? $q->whereHas(array_shift($relations), $closure) : $q->has(array_shift($relations), $operator, $count, 'and', $callback); }; return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure); } /** * Add a relationship count / exists condition to the query with an "or". * * @param string $relation * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function orHas($relation, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'or'); } /** * Add a relationship count / exists condition to the query. * * @param string $relation * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function doesntHave($relation, $boolean = 'and', Closure $callback = null) { return $this->has($relation, '<', 1, $boolean, $callback); } /** * Add a relationship count / exists condition to the query with an "or". * * @param string $relation * @return \Illuminate\Database\Eloquent\Builder|static */ public function orDoesntHave($relation) { return $this->doesntHave($relation, 'or'); } /** * Add a relationship count / exists condition to the query with where clauses. * * @param string $relation * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'and', $callback); } /** * Add a relationship count / exists condition to the query with where clauses. * * Also load the relationship with same condition. * * @param string $relation * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function withWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) { return $this->whereHas(Str::before($relation, ':'), $callback, $operator, $count) ->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation); } /** * Add a relationship count / exists condition to the query with where clauses and an "or". * * @param string $relation * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'or', $callback); } /** * Add a relationship count / exists condition to the query with where clauses. * * @param string $relation * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereDoesntHave($relation, Closure $callback = null) { return $this->doesntHave($relation, 'and', $callback); } /** * Add a relationship count / exists condition to the query with where clauses and an "or". * * @param string $relation * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereDoesntHave($relation, Closure $callback = null) { return $this->doesntHave($relation, 'or', $callback); } /** * Add a polymorphic relationship count / exists condition to the query. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param string $operator * @param int $count * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) { if (is_string($relation)) { $relation = $this->getRelationWithoutConstraints($relation); } $types = (array) $types; if ($types === ['*']) { $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all(); } foreach ($types as &$type) { $type = Relation::getMorphedModel($type) ?? $type; } return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) { foreach ($types as $type) { $query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) { $belongsTo = $this->getBelongsToRelation($relation, $type); if ($callback) { $callback = function ($query) use ($callback, $type) { return $callback($query, $type); }; } $query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass()) ->whereHas($belongsTo, $callback, $operator, $count); }); } }, null, null, $boolean); } /** * Get the BelongsTo relationship for a single polymorphic type. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation * @param string $type * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ protected function getBelongsToRelation(MorphTo $relation, $type) { $belongsTo = Relation::noConstraints(function () use ($relation, $type) { return $this->model->belongsTo( $type, $relation->getForeignKeyName(), $relation->getOwnerKeyName() ); }); $belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery()); return $belongsTo; } /** * Add a polymorphic relationship count / exists condition to the query with an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function orHasMorph($relation, $types, $operator = '>=', $count = 1) { return $this->hasMorph($relation, $types, $operator, $count, 'or'); } /** * Add a polymorphic relationship count / exists condition to the query. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param string $boolean * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function doesntHaveMorph($relation, $types, $boolean = 'and', Closure $callback = null) { return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback); } /** * Add a polymorphic relationship count / exists condition to the query with an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @return \Illuminate\Database\Eloquent\Builder|static */ public function orDoesntHaveMorph($relation, $types) { return $this->doesntHaveMorph($relation, $types, 'or'); } /** * Add a polymorphic relationship count / exists condition to the query with where clauses. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) { return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback); } /** * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1) { return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback); } /** * Add a polymorphic relationship count / exists condition to the query with where clauses. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereDoesntHaveMorph($relation, $types, Closure $callback = null) { return $this->doesntHaveMorph($relation, $types, 'and', $callback); } /** * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or". * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null) { return $this->doesntHaveMorph($relation, $types, 'or', $callback); } /** * Add a basic where clause to a relationship query. * * @param string $relation * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereRelation($relation, $column, $operator = null, $value = null) { return $this->whereHas($relation, function ($query) use ($column, $operator, $value) { if ($column instanceof Closure) { $column($query); } else { $query->where($column, $operator, $value); } }); } /** * Add an "or where" clause to a relationship query. * * @param string $relation * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereRelation($relation, $column, $operator = null, $value = null) { return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) { if ($column instanceof Closure) { $column($query); } else { $query->where($column, $operator, $value); } }); } /** * Add a polymorphic relationship condition to the query with a where clause. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null) { return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) { $query->where($column, $operator, $value); }); } /** * Add a polymorphic relationship condition to the query with an "or where" clause. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param string|array $types * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null) { return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) { $query->where($column, $operator, $value); }); } /** * Add a morph-to relationship condition to the query. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param \Illuminate\Database\Eloquent\Model|string|null $model * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereMorphedTo($relation, $model, $boolean = 'and') { if (is_string($relation)) { $relation = $this->getRelationWithoutConstraints($relation); } if (is_null($model)) { return $this->whereNull($relation->getMorphType(), $boolean); } if (is_string($model)) { $morphMap = Relation::morphMap(); if (! empty($morphMap) && in_array($model, $morphMap)) { $model = array_search($model, $morphMap, true); } return $this->where($relation->getMorphType(), $model, null, $boolean); } return $this->where(function ($query) use ($relation, $model) { $query->where($relation->getMorphType(), $model->getMorphClass()) ->where($relation->getForeignKeyName(), $model->getKey()); }, null, null, $boolean); } /** * Add a not morph-to relationship condition to the query. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param \Illuminate\Database\Eloquent\Model|string $model * @return \Illuminate\Database\Eloquent\Builder|static */ public function whereNotMorphedTo($relation, $model, $boolean = 'and') { if (is_string($relation)) { $relation = $this->getRelationWithoutConstraints($relation); } if (is_string($model)) { $morphMap = Relation::morphMap(); if (! empty($morphMap) && in_array($model, $morphMap)) { $model = array_search($model, $morphMap, true); } return $this->whereNot($relation->getMorphType(), '<=>', $model, $boolean); } return $this->whereNot(function ($query) use ($relation, $model) { $query->where($relation->getMorphType(), '<=>', $model->getMorphClass()) ->where($relation->getForeignKeyName(), '<=>', $model->getKey()); }, null, null, $boolean); } /** * Add a morph-to relationship condition to the query with an "or where" clause. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param \Illuminate\Database\Eloquent\Model|string|null $model * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereMorphedTo($relation, $model) { return $this->whereMorphedTo($relation, $model, 'or'); } /** * Add a not morph-to relationship condition to the query with an "or where" clause. * * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation * @param \Illuminate\Database\Eloquent\Model|string $model * @return \Illuminate\Database\Eloquent\Builder|static */ public function orWhereNotMorphedTo($relation, $model) { return $this->whereNotMorphedTo($relation, $model, 'or'); } /** * Add a "belongs to" relationship where clause to the query. * * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model> $related * @param string|null $relationshipName * @param string $boolean * @return $this * * @throws \Illuminate\Database\Eloquent\RelationNotFoundException */ public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and') { if (! $related instanceof Collection) { $relatedCollection = $related->newCollection([$related]); } else { $relatedCollection = $related; $related = $relatedCollection->first(); } if ($relatedCollection->isEmpty()) { throw new InvalidArgumentException('Collection given to whereBelongsTo method may not be empty.'); } if ($relationshipName === null) { $relationshipName = Str::camel(class_basename($related)); } try { $relationship = $this->model->{$relationshipName}(); } catch (BadMethodCallException) { throw RelationNotFoundException::make($this->model, $relationshipName); } if (! $relationship instanceof BelongsTo) { throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class); } $this->whereIn( $relationship->getQualifiedForeignKeyName(), $relatedCollection->pluck($relationship->getOwnerKeyName())->toArray(), $boolean, ); return $this; } /** * Add an "BelongsTo" relationship with an "or where" clause to the query. * * @param \Illuminate\Database\Eloquent\Model $related * @param string|null $relationshipName * @return $this * * @throws \RuntimeException */ public function orWhereBelongsTo($related, $relationshipName = null) { return $this->whereBelongsTo($related, $relationshipName, 'or'); } /** * Add subselect queries to include an aggregate value for a relationship. * * @param mixed $relations * @param string $column * @param string $function * @return $this */ public function withAggregate($relations, $column, $function = null) { if (empty($relations)) { return $this; } if (is_null($this->query->columns)) { $this->query->select([$this->query->from.'.*']); } $relations = is_array($relations) ? $relations : [$relations]; foreach ($this->parseWithRelations($relations) as $name => $constraints) { // First we will determine if the name has been aliased using an "as" clause on the name // and if it has we will extract the actual relationship name and the desired name of // the resulting column. This allows multiple aggregates on the same relationships. $segments = explode(' ', $name); unset($alias); if (count($segments) === 3 && Str::lower($segments[1]) === 'as') { [$name, $alias] = [$segments[0], $segments[2]]; } $relation = $this->getRelationWithoutConstraints($name); if ($function) { $hashedColumn = $this->getRelationHashedColumn($column, $relation); $wrappedColumn = $this->getQuery()->getGrammar()->wrap( $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn) ); $expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn); } else { $expression = $column; } // Here, we will grab the relationship sub-query and prepare to add it to the main query // as a sub-select. First, we'll get the "has" query and use that to get the relation // sub-query. We'll format this relationship name and append this column if needed. $query = $relation->getRelationExistenceQuery( $relation->getRelated()->newQuery(), $this, new Expression($expression) )->setBindings([], 'select'); $query->callScope($constraints); $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase(); // If the query contains certain elements like orderings / more than one column selected // then we will remove those elements from the query so that it will execute properly // when given to the database. Otherwise, we may receive SQL errors or poor syntax. $query->orders = null; $query->setBindings([], 'order'); if (count($query->columns) > 1) { $query->columns = [$query->columns[0]]; $query->bindings['select'] = []; } // Finally, we will make the proper column alias to the query and run this sub-select on // the query builder. Then, we will return the builder instance back to the developer // for further constraint chaining that needs to take place on the query as needed. $alias ??= Str::snake( preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column") ); if ($function === 'exists') { $this->selectRaw( sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)), $query->getBindings() )->withCasts([$alias => 'bool']); } else { $this->selectSub( $function ? $query : $query->limit(1), $alias ); } } return $this; } /** * Get the relation hashed column name for the given column and relation. * * @param string $column * @param \Illuminate\Database\Eloquent\Relations\Relationship $relation * @return string */ protected function getRelationHashedColumn($column, $relation) { if (str_contains($column, '.')) { return $column; } return $this->getQuery()->from === $relation->getQuery()->getQuery()->from ? "{$relation->getRelationCountHash(false)}.$column" : $column; } /** * Add subselect queries to count the relations. * * @param mixed $relations * @return $this */ public function withCount($relations) { return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count'); } /** * Add subselect queries to include the max of the relation's column. * * @param string|array $relation * @param string $column * @return $this */ public function withMax($relation, $column) { return $this->withAggregate($relation, $column, 'max'); } /** * Add subselect queries to include the min of the relation's column. * * @param string|array $relation * @param string $column * @return $this */ public function withMin($relation, $column) { return $this->withAggregate($relation, $column, 'min'); } /** * Add subselect queries to include the sum of the relation's column. * * @param string|array $relation * @param string $column * @return $this */ public function withSum($relation, $column) { return $this->withAggregate($relation, $column, 'sum'); } /** * Add subselect queries to include the average of the relation's column. * * @param string|array $relation * @param string $column * @return $this */ public function withAvg($relation, $column) { return $this->withAggregate($relation, $column, 'avg'); } /** * Add subselect queries to include the existence of related models. * * @param string|array $relation * @return $this */ public function withExists($relation) { return $this->withAggregate($relation, '*', 'exists'); } /** * Add the "has" condition where clause to the query. * * @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'] ?? []; $wheres = $from->getQuery()->from !== $this->getQuery()->from ? $this->requalifyWhereTables( $from->getQuery()->wheres, $from->getQuery()->grammar->getValue($from->getQuery()->from), $this->getModel()->getTable() ) : $from->getQuery()->wheres; // Here we have some other query that we want to merge the where constraints from. We will // copy over any where constraints on the query as well as remove any global scopes the // query might have removed. Then we will return ourselves with the finished merging. return $this->withoutGlobalScopes( $from->removedScopes() )->mergeWheres( $wheres, $whereBindings ); } /** * Updates the table name for any columns with a new qualified name. * * @param array $wheres * @param string $from * @param string $to * @return array */ protected function requalifyWhereTables(array $wheres, string $from, string $to): array { return collect($wheres)->map(function ($where) use ($from, $to) { return collect($where)->map(function ($value) use ($from, $to) { return is_string($value) && str_starts_with($value, $from.'.') ? $to.'.'.Str::afterLast($value, '.') : $value; }); })->toArray(); } /** * Add a sub-query count clause to this query. * * @param \Illuminate\Database\Query\Builder $query * @param 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; } } Eloquent/Factories/BelongsToManyRelationship.php000064400000004035147536122140016102 0ustar00factory = $factory; $this->pivot = $pivot; $this->relationship = $relationship; } /** * Create the attached relationship for the given model. * * @param \Illuminate\Database\Eloquent\Model $model * @return void */ public function createFor(Model $model) { Collection::wrap($this->factory instanceof Factory ? $this->factory->create([], $model) : $this->factory)->each(function ($attachable) use ($model) { $model->{$this->relationship}()->attach( $attachable, is_callable($this->pivot) ? call_user_func($this->pivot, $model) : $this->pivot ); }); } /** * Specify the model instances to always use when creating relationships. * * @param \Illuminate\Support\Collection $recycle * @return $this */ public function recycle($recycle) { if ($this->factory instanceof Factory) { $this->factory = $this->factory->recycle($recycle); } return $this; } } Eloquent/Factories/BelongsToRelationship.php000064400000005265147536122140015263 0ustar00factory = $factory; $this->relationship = $relationship; } /** * Get the parent model attributes and resolvers for the given child model. * * @param \Illuminate\Database\Eloquent\Model $model * @return array */ public function attributesFor(Model $model) { $relationship = $model->{$this->relationship}(); return $relationship instanceof MorphTo ? [ $relationship->getMorphType() => $this->factory instanceof Factory ? $this->factory->newModel()->getMorphClass() : $this->factory->getMorphClass(), $relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()), ] : [ $relationship->getForeignKeyName() => $this->resolver($relationship->getOwnerKeyName()), ]; } /** * Get the deferred resolver for this relationship's parent ID. * * @param string|null $key * @return \Closure */ protected function resolver($key) { return function () use ($key) { if (! $this->resolved) { $instance = $this->factory instanceof Factory ? ($this->factory->getRandomRecycledModel($this->factory->modelName()) ?? $this->factory->create()) : $this->factory; return $this->resolved = $key ? $instance->{$key} : $instance->getKey(); } return $this->resolved; }; } /** * Specify the model instances to always use when creating relationships. * * @param \Illuminate\Support\Collection $recycle * @return $this */ public function recycle($recycle) { if ($this->factory instanceof Factory) { $this->factory = $this->factory->recycle($recycle); } return $this; } } Eloquent/Factories/CrossJoinSequence.php000064400000001035147536122140014376 0ustar00 */ protected $model; /** * The number of models that should be generated. * * @var int|null */ protected $count; /** * The state transformations that will be applied to the model. * * @var \Illuminate\Support\Collection */ protected $states; /** * The parent relationships that will be applied to the model. * * @var \Illuminate\Support\Collection */ protected $has; /** * The child relationships that will be applied to the model. * * @var \Illuminate\Support\Collection */ protected $for; /** * The model instances to always use when creating relationships. * * @var \Illuminate\Support\Collection */ protected $recycle; /** * The "after making" callbacks that will be applied to the model. * * @var \Illuminate\Support\Collection */ protected $afterMaking; /** * The "after creating" callbacks that will be applied to the model. * * @var \Illuminate\Support\Collection */ protected $afterCreating; /** * The name of the database connection that will be used to create the models. * * @var string|null */ protected $connection; /** * The current Faker instance. * * @var \Faker\Generator */ protected $faker; /** * The default namespace where factories reside. * * @var string */ public static $namespace = 'Database\\Factories\\'; /** * The default model name resolver. * * @var callable */ protected static $modelNameResolver; /** * The factory name resolver. * * @var callable */ protected static $factoryNameResolver; /** * Create a new factory instance. * * @param int|null $count * @param \Illuminate\Support\Collection|null $states * @param \Illuminate\Support\Collection|null $has * @param \Illuminate\Support\Collection|null $for * @param \Illuminate\Support\Collection|null $afterMaking * @param \Illuminate\Support\Collection|null $afterCreating * @param string|null $connection * @param \Illuminate\Support\Collection|null $recycle * @return void */ public function __construct($count = null, ?Collection $states = null, ?Collection $has = null, ?Collection $for = null, ?Collection $afterMaking = null, ?Collection $afterCreating = null, $connection = null, ?Collection $recycle = null) { $this->count = $count; $this->states = $states ?? new Collection; $this->has = $has ?? new Collection; $this->for = $for ?? new Collection; $this->afterMaking = $afterMaking ?? new Collection; $this->afterCreating = $afterCreating ?? new Collection; $this->connection = $connection; $this->recycle = $recycle ?? new Collection; $this->faker = $this->withFaker(); } /** * Define the model's default state. * * @return array */ abstract public function definition(); /** * Get a new factory instance for the given attributes. * * @param (callable(array): array)|array $attributes * @return static */ public static function new($attributes = []) { return (new static)->state($attributes)->configure(); } /** * Get a new factory instance for the given number of models. * * @param int $count * @return static */ public static function times(int $count) { return static::new()->count($count); } /** * Configure the factory. * * @return static */ public function configure() { return $this; } /** * Get the raw attributes generated by the factory. * * @param (callable(array): array)|array $attributes * @param \Illuminate\Database\Eloquent\Model|null $parent * @return array */ public function raw($attributes = [], ?Model $parent = null) { if ($this->count === null) { return $this->state($attributes)->getExpandedAttributes($parent); } return array_map(function () use ($attributes, $parent) { return $this->state($attributes)->getExpandedAttributes($parent); }, range(1, $this->count)); } /** * Create a single model and persist it to the database. * * @param (callable(array): array)|array $attributes * @return \Illuminate\Database\Eloquent\Model|TModel */ public function createOne($attributes = []) { return $this->count(null)->create($attributes); } /** * Create a single model and persist it to the database without dispatching any model events. * * @param (callable(array): array)|array $attributes * @return \Illuminate\Database\Eloquent\Model|TModel */ public function createOneQuietly($attributes = []) { return $this->count(null)->createQuietly($attributes); } /** * Create a collection of models and persist them to the database. * * @param int|null|iterable> $records * @return \Illuminate\Database\Eloquent\Collection */ public function createMany(int|iterable|null $records = null) { if (is_null($records)) { $records = $this->count ?? 1; } if (is_numeric($records)) { $records = array_fill(0, $records, []); } return new EloquentCollection( collect($records)->map(function ($record) { return $this->state($record)->create(); }) ); } /** * Create a collection of models and persist them to the database without dispatching any model events. * * @param int|null|iterable> $records * @return \Illuminate\Database\Eloquent\Collection */ public function createManyQuietly(int|iterable|null $records = null) { return Model::withoutEvents(function () use ($records) { return $this->createMany($records); }); } /** * Create a collection of models and persist them to the database. * * @param (callable(array): array)|array $attributes * @param \Illuminate\Database\Eloquent\Model|null $parent * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|TModel */ public function create($attributes = [], ?Model $parent = null) { if (! empty($attributes)) { return $this->state($attributes)->create([], $parent); } $results = $this->make($attributes, $parent); if ($results instanceof Model) { $this->store(collect([$results])); $this->callAfterCreating(collect([$results]), $parent); } else { $this->store($results); $this->callAfterCreating($results, $parent); } return $results; } /** * Create a collection of models and persist them to the database without dispatching any model events. * * @param (callable(array): array)|array $attributes * @param \Illuminate\Database\Eloquent\Model|null $parent * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|TModel */ public function createQuietly($attributes = [], ?Model $parent = null) { return Model::withoutEvents(function () use ($attributes, $parent) { return $this->create($attributes, $parent); }); } /** * Create a callback that persists a model in the database when invoked. * * @param array $attributes * @param \Illuminate\Database\Eloquent\Model|null $parent * @return \Closure(): (\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|TModel) */ public function lazy(array $attributes = [], ?Model $parent = null) { return fn () => $this->create($attributes, $parent); } /** * Set the connection name on the results and store them. * * @param \Illuminate\Support\Collection $results * @return void */ protected function store(Collection $results) { $results->each(function ($model) { if (! isset($this->connection)) { $model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName()); } $model->save(); foreach ($model->getRelations() as $name => $items) { if ($items instanceof Enumerable && $items->isEmpty()) { $model->unsetRelation($name); } } $this->createChildren($model); }); } /** * Create the children for the given model. * * @param \Illuminate\Database\Eloquent\Model $model * @return void */ protected function createChildren(Model $model) { Model::unguarded(function () use ($model) { $this->has->each(function ($has) use ($model) { $has->recycle($this->recycle)->createFor($model); }); }); } /** * Make a single instance of the model. * * @param (callable(array): array)|array $attributes * @return \Illuminate\Database\Eloquent\Model|TModel */ public function makeOne($attributes = []) { return $this->count(null)->make($attributes); } /** * Create a collection of models. * * @param (callable(array): array)|array $attributes * @param \Illuminate\Database\Eloquent\Model|null $parent * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|TModel */ public function make($attributes = [], ?Model $parent = null) { if (! empty($attributes)) { return $this->state($attributes)->make([], $parent); } if ($this->count === null) { return tap($this->makeInstance($parent), function ($instance) { $this->callAfterMaking(collect([$instance])); }); } if ($this->count < 1) { return $this->newModel()->newCollection(); } $instances = $this->newModel()->newCollection(array_map(function () use ($parent) { return $this->makeInstance($parent); }, range(1, $this->count))); $this->callAfterMaking($instances); return $instances; } /** * Make an instance of the model with the given attributes. * * @param \Illuminate\Database\Eloquent\Model|null $parent * @return \Illuminate\Database\Eloquent\Model */ protected function makeInstance(?Model $parent) { return Model::unguarded(function () use ($parent) { return tap($this->newModel($this->getExpandedAttributes($parent)), function ($instance) { if (isset($this->connection)) { $instance->setConnection($this->connection); } }); }); } /** * Get a raw attributes array for the model. * * @param \Illuminate\Database\Eloquent\Model|null $parent * @return mixed */ protected function getExpandedAttributes(?Model $parent) { return $this->expandAttributes($this->getRawAttributes($parent)); } /** * Get the raw attributes for the model as an array. * * @param \Illuminate\Database\Eloquent\Model|null $parent * @return array */ protected function getRawAttributes(?Model $parent) { return $this->states->pipe(function ($states) { return $this->for->isEmpty() ? $states : new Collection(array_merge([function () { return $this->parentResolvers(); }], $states->all())); })->reduce(function ($carry, $state) use ($parent) { if ($state instanceof Closure) { $state = $state->bindTo($this); } return array_merge($carry, $state($carry, $parent)); }, $this->definition()); } /** * Create the parent relationship resolvers (as deferred Closures). * * @return array */ protected function parentResolvers() { $model = $this->newModel(); return $this->for->map(function (BelongsToRelationship $for) use ($model) { return $for->recycle($this->recycle)->attributesFor($model); })->collapse()->all(); } /** * Expand all attributes to their underlying values. * * @param array $definition * @return array */ protected function expandAttributes(array $definition) { return collect($definition) ->map($evaluateRelations = function ($attribute) { if ($attribute instanceof self) { $attribute = $this->getRandomRecycledModel($attribute->modelName())?->getKey() ?? $attribute->recycle($this->recycle)->create()->getKey(); } elseif ($attribute instanceof Model) { $attribute = $attribute->getKey(); } return $attribute; }) ->map(function ($attribute, $key) use (&$definition, $evaluateRelations) { if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) { $attribute = $attribute($definition); } $attribute = $evaluateRelations($attribute); $definition[$key] = $attribute; return $attribute; }) ->all(); } /** * Add a new state transformation to the model definition. * * @param (callable(array, \Illuminate\Database\Eloquent\Model|null): array)|array $state * @return static */ public function state($state) { return $this->newInstance([ 'states' => $this->states->concat([ is_callable($state) ? $state : function () use ($state) { return $state; }, ]), ]); } /** * Set a single model attribute. * * @param string|int $key * @param mixed $value * @return static */ public function set($key, $value) { return $this->state([$key => $value]); } /** * Add a new sequenced state transformation to the model definition. * * @param mixed ...$sequence * @return static */ public function sequence(...$sequence) { return $this->state(new Sequence(...$sequence)); } /** * Add a new sequenced state transformation to the model definition and update the pending creation count to the size of the sequence. * * @param array ...$sequence * @return static */ public function forEachSequence(...$sequence) { return $this->state(new Sequence(...$sequence))->count(count($sequence)); } /** * Add a new cross joined sequenced state transformation to the model definition. * * @param array ...$sequence * @return static */ public function crossJoinSequence(...$sequence) { return $this->state(new CrossJoinSequence(...$sequence)); } /** * Define a child relationship for the model. * * @param \Illuminate\Database\Eloquent\Factories\Factory $factory * @param string|null $relationship * @return static */ public function has(self $factory, $relationship = null) { return $this->newInstance([ 'has' => $this->has->concat([new Relationship( $factory, $relationship ?? $this->guessRelationship($factory->modelName()) )]), ]); } /** * Attempt to guess the relationship name for a "has" relationship. * * @param string $related * @return string */ protected function guessRelationship(string $related) { $guess = Str::camel(Str::plural(class_basename($related))); return method_exists($this->modelName(), $guess) ? $guess : Str::singular($guess); } /** * Define an attached relationship for the model. * * @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $factory * @param (callable(): array)|array $pivot * @param string|null $relationship * @return static */ public function hasAttached($factory, $pivot = [], $relationship = null) { return $this->newInstance([ 'has' => $this->has->concat([new BelongsToManyRelationship( $factory, $pivot, $relationship ?? Str::camel(Str::plural(class_basename( $factory instanceof Factory ? $factory->modelName() : Collection::wrap($factory)->first() ))) )]), ]); } /** * Define a parent relationship for the model. * * @param \Illuminate\Database\Eloquent\Factories\Factory|\Illuminate\Database\Eloquent\Model $factory * @param string|null $relationship * @return static */ public function for($factory, $relationship = null) { return $this->newInstance(['for' => $this->for->concat([new BelongsToRelationship( $factory, $relationship ?? Str::camel(class_basename( $factory instanceof Factory ? $factory->modelName() : $factory )) )])]); } /** * Provide model instances to use instead of any nested factory calls when creating relationships. * * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Support\Collection|array $model * @return static */ public function recycle($model) { // Group provided models by the type and merge them into existing recycle collection return $this->newInstance([ 'recycle' => $this->recycle ->flatten() ->merge( Collection::wrap($model instanceof Model ? func_get_args() : $model) ->flatten() )->groupBy(fn ($model) => get_class($model)), ]); } /** * Retrieve a random model of a given type from previously provided models to recycle. * * @param string $modelClassName * @return \Illuminate\Database\Eloquent\Model|null */ public function getRandomRecycledModel($modelClassName) { return $this->recycle->get($modelClassName)?->random(); } /** * Add a new "after making" callback to the model definition. * * @param \Closure(\Illuminate\Database\Eloquent\Model|TModel): mixed $callback * @return static */ public function afterMaking(Closure $callback) { return $this->newInstance(['afterMaking' => $this->afterMaking->concat([$callback])]); } /** * Add a new "after creating" callback to the model definition. * * @param \Closure(\Illuminate\Database\Eloquent\Model|TModel): mixed $callback * @return static */ public function afterCreating(Closure $callback) { return $this->newInstance(['afterCreating' => $this->afterCreating->concat([$callback])]); } /** * Call the "after making" callbacks for the given model instances. * * @param \Illuminate\Support\Collection $instances * @return void */ protected function callAfterMaking(Collection $instances) { $instances->each(function ($model) { $this->afterMaking->each(function ($callback) use ($model) { $callback($model); }); }); } /** * Call the "after creating" callbacks for the given model instances. * * @param \Illuminate\Support\Collection $instances * @param \Illuminate\Database\Eloquent\Model|null $parent * @return void */ protected function callAfterCreating(Collection $instances, ?Model $parent = null) { $instances->each(function ($model) use ($parent) { $this->afterCreating->each(function ($callback) use ($model, $parent) { $callback($model, $parent); }); }); } /** * Specify how many models should be generated. * * @param int|null $count * @return static */ public function count(?int $count) { return $this->newInstance(['count' => $count]); } /** * Specify the database connection that should be used to generate models. * * @param string $connection * @return static */ public function connection(string $connection) { return $this->newInstance(['connection' => $connection]); } /** * Create a new instance of the factory builder with the given mutated properties. * * @param array $arguments * @return static */ protected function newInstance(array $arguments = []) { return new static(...array_values(array_merge([ 'count' => $this->count, 'states' => $this->states, 'has' => $this->has, 'for' => $this->for, 'afterMaking' => $this->afterMaking, 'afterCreating' => $this->afterCreating, 'connection' => $this->connection, 'recycle' => $this->recycle, ], $arguments))); } /** * Get a new model instance. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model|TModel */ public function newModel(array $attributes = []) { $model = $this->modelName(); return new $model($attributes); } /** * Get the name of the model that is generated by the factory. * * @return class-string<\Illuminate\Database\Eloquent\Model|TModel> */ public function modelName() { $resolver = static::$modelNameResolver ?? function (self $factory) { $namespacedFactoryBasename = Str::replaceLast( 'Factory', '', Str::replaceFirst(static::$namespace, '', get_class($factory)) ); $factoryBasename = Str::replaceLast('Factory', '', class_basename($factory)); $appNamespace = static::appNamespace(); return class_exists($appNamespace.'Models\\'.$namespacedFactoryBasename) ? $appNamespace.'Models\\'.$namespacedFactoryBasename : $appNamespace.$factoryBasename; }; return $this->model ?? $resolver($this); } /** * Specify the callback that should be invoked to guess model names based on factory names. * * @param callable(self): class-string<\Illuminate\Database\Eloquent\Model|TModel> $callback * @return void */ public static function guessModelNamesUsing(callable $callback) { static::$modelNameResolver = $callback; } /** * Specify the default namespace that contains the application's model factories. * * @param string $namespace * @return void */ public static function useNamespace(string $namespace) { static::$namespace = $namespace; } /** * Get a new factory instance for the given model name. * * @param class-string<\Illuminate\Database\Eloquent\Model> $modelName * @return \Illuminate\Database\Eloquent\Factories\Factory */ public static function factoryForModel(string $modelName) { $factory = static::resolveFactoryName($modelName); return $factory::new(); } /** * Specify the callback that should be invoked to guess factory names based on dynamic relationship names. * * @param callable(class-string<\Illuminate\Database\Eloquent\Model>): class-string<\Illuminate\Database\Eloquent\Factories\Factory> $callback * @return void */ public static function guessFactoryNamesUsing(callable $callback) { static::$factoryNameResolver = $callback; } /** * Get a new Faker instance. * * @return \Faker\Generator */ protected function withFaker() { return Container::getInstance()->make(Generator::class); } /** * Get the factory name for the given model name. * * @param class-string<\Illuminate\Database\Eloquent\Model> $modelName * @return class-string<\Illuminate\Database\Eloquent\Factories\Factory> */ public static function resolveFactoryName(string $modelName) { $resolver = static::$factoryNameResolver ?? function (string $modelName) { $appNamespace = static::appNamespace(); $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') ? Str::after($modelName, $appNamespace.'Models\\') : Str::after($modelName, $appNamespace); return static::$namespace.$modelName.'Factory'; }; return $resolver($modelName); } /** * Get the application namespace for the application. * * @return string */ protected static function appNamespace() { try { return Container::getInstance() ->make(Application::class) ->getNamespace(); } catch (Throwable) { return 'App\\'; } } /** * Proxy dynamic factory methods onto their proper methods. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } if ($method === 'trashed' && in_array(SoftDeletes::class, class_uses_recursive($this->modelName()))) { return $this->state([ $this->newModel()->getDeletedAtColumn() => $parameters[0] ?? Carbon::now()->subDay(), ]); } if (! Str::startsWith($method, ['for', 'has'])) { static::throwBadMethodCallException($method); } $relationship = Str::camel(Str::substr($method, 3)); $relatedModel = get_class($this->newModel()->{$relationship}()->getRelated()); if (method_exists($relatedModel, 'newFactory')) { $factory = $relatedModel::newFactory() ?? static::factoryForModel($relatedModel); } else { $factory = static::factoryForModel($relatedModel); } if (str_starts_with($method, 'for')) { return $this->for($factory->state($parameters[0] ?? []), $relationship); } elseif (str_starts_with($method, 'has')) { return $this->has( $factory ->count(is_numeric($parameters[0] ?? null) ? $parameters[0] : 1) ->state((is_callable($parameters[0] ?? null) || is_array($parameters[0] ?? null)) ? $parameters[0] : ($parameters[1] ?? [])), $relationship ); } } } Eloquent/Factories/HasFactory.php000064400000001535147536122140013044 0ustar00 */ public static function factory($count = null, $state = []) { $factory = static::newFactory() ?: Factory::factoryForModel(get_called_class()); return $factory ->count(is_numeric($count) ? $count : null) ->state(is_callable($count) || is_array($count) ? $count : $state); } /** * Create a new factory instance for the model. * * @return \Illuminate\Database\Eloquent\Factories\Factory */ protected static function newFactory() { // } } Eloquent/Factories/Relationship.php000064400000004150147536122140013436 0ustar00factory = $factory; $this->relationship = $relationship; } /** * Create the child relationship for the given parent model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return void */ public function createFor(Model $parent) { $relationship = $parent->{$this->relationship}(); if ($relationship instanceof MorphOneOrMany) { $this->factory->state([ $relationship->getMorphType() => $relationship->getMorphClass(), $relationship->getForeignKeyName() => $relationship->getParentKey(), ])->create([], $parent); } elseif ($relationship instanceof HasOneOrMany) { $this->factory->state([ $relationship->getForeignKeyName() => $relationship->getParentKey(), ])->create([], $parent); } elseif ($relationship instanceof BelongsToMany) { $relationship->attach($this->factory->create([], $parent)); } } /** * Specify the model instances to always use when creating relationships. * * @param \Illuminate\Support\Collection $recycle * @return $this */ public function recycle($recycle) { $this->factory = $this->factory->recycle($recycle); return $this; } } Eloquent/Factories/Sequence.php000064400000002160147536122140012544 0ustar00sequence = $sequence; $this->count = count($sequence); } /** * Get the current count of the sequence items. * * @return int */ public function count(): int { return $this->count; } /** * Get the next value in the sequence. * * @return mixed */ public function __invoke() { return tap(value($this->sequence[$this->index % $this->count], $this), function () { $this->index = $this->index + 1; }); } } Eloquent/HigherOrderBuilderProxy.php000064400000002055147536122140013633 0ustar00method = $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); }); } } Eloquent/InvalidCastException.php000064400000001644147536122140013143 0ustar00model = $class; $this->column = $column; $this->castType = $castType; } } Eloquent/JsonEncodingException.php000064400000002555147536122140013324 0ustar00getKey().'] 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}."); } } Eloquent/MassAssignmentException.php000075500000000211147536122140013666 0ustar00prunable(), function ($query) use ($chunkSize) { $query->when(! $query->getQuery()->limit, function ($query) use ($chunkSize) { $query->limit($chunkSize); }); }); $total = 0; do { $total += $count = in_array(SoftDeletes::class, class_uses_recursive(get_class($this))) ? $query->forceDelete() : $query->delete(); if ($count > 0) { event(new ModelsPruned(static::class, $total)); } } while ($count > 0); return $total; } /** * Get the prunable model query. * * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { throw new LogicException('Please implement the prunable method on your model.'); } } Eloquent/MissingAttributeException.php000075500000001064147536122140014236 0ustar00bootIfNotBooted(); $this->initializeTraits(); $this->syncOriginal(); $this->fill($attributes); } /** * Check if the model needs to be booted and if so, do it. * * @return void */ protected function bootIfNotBooted() { if (! isset(static::$booted[static::class])) { static::$booted[static::class] = true; $this->fireModelEvent('booting', false); static::booting(); static::boot(); static::booted(); $this->fireModelEvent('booted', false); } } /** * Perform any actions required before the model boots. * * @return void */ protected static function booting() { // } /** * Bootstrap the model and its traits. * * @return void */ protected static function boot() { static::bootTraits(); } /** * Boot all of the bootable traits on the model. * * @return void */ protected static function bootTraits() { $class = static::class; $booted = []; static::$traitInitializers[$class] = []; foreach (class_uses_recursive($class) as $trait) { $method = 'boot'.class_basename($trait); if (method_exists($class, $method) && ! in_array($method, $booted)) { forward_static_call([$class, $method]); $booted[] = $method; } if (method_exists($class, $method = 'initialize'.class_basename($trait))) { static::$traitInitializers[$class][] = $method; static::$traitInitializers[$class] = array_unique( static::$traitInitializers[$class] ); } } } /** * Initialize any initializable traits on the model. * * @return void */ protected function initializeTraits() { foreach (static::$traitInitializers[static::class] as $method) { $this->{$method}(); } } /** * Perform any actions required after the model boots. * * @return void */ protected static function booted() { // } /** * Clear the list of booted models so they will be re-booted. * * @return void */ public static function clearBootedModels() { static::$booted = []; static::$globalScopes = []; } /** * Disables relationship model touching for the current class during given callback scope. * * @param callable $callback * @return void */ public static function withoutTouching(callable $callback) { static::withoutTouchingOn([static::class], $callback); } /** * Disables relationship model touching for the given model classes during given callback scope. * * @param array $models * @param callable $callback * @return void */ public static function withoutTouchingOn(array $models, callable $callback) { static::$ignoreOnTouch = array_values(array_merge(static::$ignoreOnTouch, $models)); try { $callback(); } finally { static::$ignoreOnTouch = array_values(array_diff(static::$ignoreOnTouch, $models)); } } /** * Determine if the given model is ignoring touches. * * @param string|null $class * @return bool */ public static function isIgnoringTouch($class = null) { $class = $class ?: static::class; if (! get_class_vars($class)['timestamps'] || ! $class::UPDATED_AT) { return true; } foreach (static::$ignoreOnTouch as $ignoredClass) { if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) { return true; } } return false; } /** * Indicate that models should prevent lazy loading, silently discarding attributes, and accessing missing attributes. * * @param bool $shouldBeStrict * @return void */ public static function shouldBeStrict(bool $shouldBeStrict = true) { static::preventLazyLoading($shouldBeStrict); static::preventSilentlyDiscardingAttributes($shouldBeStrict); static::preventAccessingMissingAttributes($shouldBeStrict); } /** * Prevent model relationships from being lazy loaded. * * @param bool $value * @return void */ public static function preventLazyLoading($value = true) { static::$modelsShouldPreventLazyLoading = $value; } /** * Register a callback that is responsible for handling lazy loading violations. * * @param callable|null $callback * @return void */ public static function handleLazyLoadingViolationUsing(?callable $callback) { static::$lazyLoadingViolationCallback = $callback; } /** * Prevent non-fillable attributes from being silently discarded. * * @param bool $value * @return void */ public static function preventSilentlyDiscardingAttributes($value = true) { static::$modelsShouldPreventSilentlyDiscardingAttributes = $value; } /** * Register a callback that is responsible for handling discarded attribute violations. * * @param callable|null $callback * @return void */ public static function handleDiscardedAttributeViolationUsing(?callable $callback) { static::$discardedAttributeViolationCallback = $callback; } /** * Prevent accessing missing attributes on retrieved models. * * @param bool $value * @return void */ public static function preventAccessingMissingAttributes($value = true) { static::$modelsShouldPreventAccessingMissingAttributes = $value; } /** * Register a callback that is responsible for handling missing attribute violations. * * @param callable|null $callback * @return void */ public static function handleMissingAttributeViolationUsing(?callable $callback) { static::$missingAttributeViolationCallback = $callback; } /** * Execute a callback without broadcasting any model events for all model types. * * @param callable $callback * @return mixed */ public static function withoutBroadcasting(callable $callback) { $isBroadcasting = static::$isBroadcasting; static::$isBroadcasting = false; try { return $callback(); } finally { static::$isBroadcasting = $isBroadcasting; } } /** * Fill the model with an array of attributes. * * @param array $attributes * @return $this * * @throws \Illuminate\Database\Eloquent\MassAssignmentException */ public function fill(array $attributes) { $totallyGuarded = $this->totallyGuarded(); $fillable = $this->fillableFromArray($attributes); foreach ($fillable as $key => $value) { // The developers may choose to place some attributes in the "fillable" array // which means only those attributes may be set through mass assignment to // the model, and all others will just get ignored for security reasons. if ($this->isFillable($key)) { $this->setAttribute($key, $value); } elseif ($totallyGuarded || static::preventsSilentlyDiscardingAttributes()) { if (isset(static::$discardedAttributeViolationCallback)) { call_user_func(static::$discardedAttributeViolationCallback, $this, [$key]); } else { throw new MassAssignmentException(sprintf( 'Add [%s] to fillable property to allow mass assignment on [%s].', $key, get_class($this) )); } } } if (count($attributes) !== count($fillable) && static::preventsSilentlyDiscardingAttributes()) { $keys = array_diff(array_keys($attributes), array_keys($fillable)); if (isset(static::$discardedAttributeViolationCallback)) { call_user_func(static::$discardedAttributeViolationCallback, $this, $keys); } else { throw new MassAssignmentException(sprintf( 'Add fillable property [%s] to allow mass assignment on [%s].', implode(', ', $keys), get_class($this) )); } } return $this; } /** * Fill the model with an array of attributes. Force mass assignment. * * @param array $attributes * @return $this */ public function forceFill(array $attributes) { return static::unguarded(fn () => $this->fill($attributes)); } /** * Qualify the given column name by the model's table. * * @param string $column * @return string */ public function qualifyColumn($column) { if (str_contains($column, '.')) { return $column; } return $this->getTable().'.'.$column; } /** * Qualify the given columns with the model's table. * * @param array $columns * @return array */ public function qualifyColumns($columns) { return collect($columns)->map(function ($column) { return $this->qualifyColumn($column); })->all(); } /** * Create a new instance of the given model. * * @param array $attributes * @param bool $exists * @return static */ public function newInstance($attributes = [], $exists = false) { // This method just provides a convenient way for us to generate fresh model // instances of this current model. It is particularly useful during the // hydration of new objects via the Eloquent query builder instances. $model = new static; $model->exists = $exists; $model->setConnection( $this->getConnectionName() ); $model->setTable($this->getTable()); $model->mergeCasts($this->casts); $model->fill((array) $attributes); return $model; } /** * Create a new model instance that is existing. * * @param array $attributes * @param string|null $connection * @return static */ public function newFromBuilder($attributes = [], $connection = null) { $model = $this->newInstance([], true); $model->setRawAttributes((array) $attributes, true); $model->setConnection($connection ?: $this->getConnectionName()); $model->fireModelEvent('retrieved', false); return $model; } /** * Begin querying the model on a given connection. * * @param string|null $connection * @return \Illuminate\Database\Eloquent\Builder */ public static function on($connection = null) { // First we will just create a fresh instance of this model, and then we can set the // connection on the model so that it is used for the queries we execute, as well // as being set on every relation we retrieve without a custom connection name. $instance = new static; $instance->setConnection($connection); return $instance->newQuery(); } /** * Begin querying the model on the write connection. * * @return \Illuminate\Database\Eloquent\Builder */ public static function onWriteConnection() { return static::query()->useWritePdo(); } /** * Get all of the models from the database. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Collection */ public static function all($columns = ['*']) { return static::query()->get( is_array($columns) ? $columns : func_get_args() ); } /** * Begin querying a model with eager loading. * * @param array|string $relations * @return \Illuminate\Database\Eloquent\Builder */ public static function with($relations) { return static::query()->with( is_string($relations) ? func_get_args() : $relations ); } /** * Eager load relations on the model. * * @param array|string $relations * @return $this */ public function load($relations) { $query = $this->newQueryWithoutRelationships()->with( is_string($relations) ? func_get_args() : $relations ); $query->eagerLoadRelations([$this]); return $this; } /** * Eager load relationships on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @return $this */ public function loadMorph($relation, $relations) { if (! $this->{$relation}) { return $this; } $className = get_class($this->{$relation}); $this->{$relation}->load($relations[$className] ?? []); return $this; } /** * Eager load relations on the model if they are not already eager loaded. * * @param array|string $relations * @return $this */ public function loadMissing($relations) { $relations = is_string($relations) ? func_get_args() : $relations; $this->newCollection([$this])->loadMissing($relations); return $this; } /** * Eager load relation's column aggregations on the model. * * @param array|string $relations * @param string $column * @param string|null $function * @return $this */ public function loadAggregate($relations, $column, $function = null) { $this->newCollection([$this])->loadAggregate($relations, $column, $function); return $this; } /** * Eager load relation counts on the model. * * @param array|string $relations * @return $this */ public function loadCount($relations) { $relations = is_string($relations) ? func_get_args() : $relations; return $this->loadAggregate($relations, '*', 'count'); } /** * Eager load relation max column values on the model. * * @param array|string $relations * @param string $column * @return $this */ public function loadMax($relations, $column) { return $this->loadAggregate($relations, $column, 'max'); } /** * Eager load relation min column values on the model. * * @param array|string $relations * @param string $column * @return $this */ public function loadMin($relations, $column) { return $this->loadAggregate($relations, $column, 'min'); } /** * Eager load relation's column summations on the model. * * @param array|string $relations * @param string $column * @return $this */ public function loadSum($relations, $column) { return $this->loadAggregate($relations, $column, 'sum'); } /** * Eager load relation average column values on the model. * * @param array|string $relations * @param string $column * @return $this */ public function loadAvg($relations, $column) { return $this->loadAggregate($relations, $column, 'avg'); } /** * Eager load related model existence values on the model. * * @param array|string $relations * @return $this */ public function loadExists($relations) { return $this->loadAggregate($relations, '*', 'exists'); } /** * Eager load relationship column aggregation on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @param string $column * @param string|null $function * @return $this */ public function loadMorphAggregate($relation, $relations, $column, $function = null) { if (! $this->{$relation}) { return $this; } $className = get_class($this->{$relation}); $this->{$relation}->loadAggregate($relations[$className] ?? [], $column, $function); return $this; } /** * Eager load relationship counts on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @return $this */ public function loadMorphCount($relation, $relations) { return $this->loadMorphAggregate($relation, $relations, '*', 'count'); } /** * Eager load relationship max column values on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @param string $column * @return $this */ public function loadMorphMax($relation, $relations, $column) { return $this->loadMorphAggregate($relation, $relations, $column, 'max'); } /** * Eager load relationship min column values on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @param string $column * @return $this */ public function loadMorphMin($relation, $relations, $column) { return $this->loadMorphAggregate($relation, $relations, $column, 'min'); } /** * Eager load relationship column summations on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @param string $column * @return $this */ public function loadMorphSum($relation, $relations, $column) { return $this->loadMorphAggregate($relation, $relations, $column, 'sum'); } /** * Eager load relationship average column values on the polymorphic relation of a model. * * @param string $relation * @param array $relations * @param string $column * @return $this */ public function loadMorphAvg($relation, $relations, $column) { return $this->loadMorphAggregate($relation, $relations, $column, 'avg'); } /** * Increment a column's value by a given amount. * * @param string $column * @param float|int $amount * @param array $extra * @return int */ protected function increment($column, $amount = 1, array $extra = []) { return $this->incrementOrDecrement($column, $amount, $extra, 'increment'); } /** * Decrement a column's value by a given amount. * * @param string $column * @param float|int $amount * @param array $extra * @return int */ protected function decrement($column, $amount = 1, array $extra = []) { return $this->incrementOrDecrement($column, $amount, $extra, 'decrement'); } /** * Run the increment or decrement method on the model. * * @param string $column * @param float|int $amount * @param array $extra * @param string $method * @return int */ protected function incrementOrDecrement($column, $amount, $extra, $method) { if (! $this->exists) { return $this->newQueryWithoutRelationships()->{$method}($column, $amount, $extra); } $this->{$column} = $this->isClassDeviable($column) ? $this->deviateClassCastableAttribute($method, $column, $amount) : $this->{$column} + ($method === 'increment' ? $amount : $amount * -1); $this->forceFill($extra); if ($this->fireModelEvent('updating') === false) { return false; } return tap($this->setKeysForSaveQuery($this->newQueryWithoutScopes())->{$method}($column, $amount, $extra), function () use ($column) { $this->syncChanges(); $this->fireModelEvent('updated', false); $this->syncOriginalAttribute($column); }); } /** * Update the model in the database. * * @param array $attributes * @param array $options * @return bool */ public function update(array $attributes = [], array $options = []) { if (! $this->exists) { return false; } return $this->fill($attributes)->save($options); } /** * Update the model in the database within a transaction. * * @param array $attributes * @param array $options * @return bool * * @throws \Throwable */ public function updateOrFail(array $attributes = [], array $options = []) { if (! $this->exists) { return false; } return $this->fill($attributes)->saveOrFail($options); } /** * Update the model in the database without raising any events. * * @param array $attributes * @param array $options * @return bool */ public function updateQuietly(array $attributes = [], array $options = []) { if (! $this->exists) { return false; } return $this->fill($attributes)->saveQuietly($options); } /** * Increment a column's value by a given amount without raising any events. * * @param string $column * @param float|int $amount * @param array $extra * @return int */ protected function incrementQuietly($column, $amount = 1, array $extra = []) { return static::withoutEvents(function () use ($column, $amount, $extra) { return $this->incrementOrDecrement($column, $amount, $extra, 'increment'); }); } /** * Decrement a column's value by a given amount without raising any events. * * @param string $column * @param float|int $amount * @param array $extra * @return int */ protected function decrementQuietly($column, $amount = 1, array $extra = []) { return static::withoutEvents(function () use ($column, $amount, $extra) { return $this->incrementOrDecrement($column, $amount, $extra, 'decrement'); }); } /** * Save the model and all of its relationships. * * @return bool */ public function push() { if (! $this->save()) { return false; } // To sync all of the relationships to the database, we will simply spin through // the relationships and save each model via this "push" method, which allows // us to recurse into all of these nested relations for the model instance. foreach ($this->relations as $models) { $models = $models instanceof Collection ? $models->all() : [$models]; foreach (array_filter($models) as $model) { if (! $model->push()) { return false; } } } return true; } /** * Save the model and all of its relationships without raising any events to the parent model. * * @return bool */ public function pushQuietly() { return static::withoutEvents(fn () => $this->push()); } /** * Save the model to the database without raising any events. * * @param array $options * @return bool */ public function saveQuietly(array $options = []) { return static::withoutEvents(fn () => $this->save($options)); } /** * Save the model to the database. * * @param array $options * @return bool */ public function save(array $options = []) { $this->mergeAttributesFromCachedCasts(); $query = $this->newModelQuery(); // If the "saving" event returns false we'll bail out of the save and return // false, indicating that the save failed. This provides a chance for any // listeners to cancel save operations if validations fail or whatever. if ($this->fireModelEvent('saving') === false) { return false; } // If the model already exists in the database we can just update our record // that is already in this database using the current IDs in this "where" // clause to only update this model. Otherwise, we'll just insert them. if ($this->exists) { $saved = $this->isDirty() ? $this->performUpdate($query) : true; } // If the model is brand new, we'll insert it into our database and set the // ID attribute on the model to the value of the newly inserted row's ID // which is typically an auto-increment value managed by the database. else { $saved = $this->performInsert($query); if (! $this->getConnectionName() && $connection = $query->getConnection()) { $this->setConnection($connection->getName()); } } // If the model is successfully saved, we need to do a few more things once // that is done. We will call the "saved" method here to run any actions // we need to happen after a model gets successfully saved right here. if ($saved) { $this->finishSave($options); } return $saved; } /** * Save the model to the database within a transaction. * * @param array $options * @return bool * * @throws \Throwable */ public function saveOrFail(array $options = []) { return $this->getConnection()->transaction(fn () => $this->save($options)); } /** * Perform any actions that are necessary after the model is saved. * * @param array $options * @return void */ protected function finishSave(array $options) { $this->fireModelEvent('saved', false); if ($this->isDirty() && ($options['touch'] ?? true)) { $this->touchOwners(); } $this->syncOriginal(); } /** * Perform a model update operation. * * @param \Illuminate\Database\Eloquent\Builder $query * @return bool */ protected function performUpdate(Builder $query) { // If the updating event returns false, we will cancel the update operation so // developers can hook Validation systems into their models and cancel this // operation if the model does not pass validation. Otherwise, we update. if ($this->fireModelEvent('updating') === false) { return false; } // First we need to create a fresh query instance and touch the creation and // update timestamp on the model which are maintained by us for developer // convenience. Then we will just continue saving the model instances. if ($this->usesTimestamps()) { $this->updateTimestamps(); } // Once we have run the update operation, we will fire the "updated" event for // this model instance. This will allow developers to hook into these after // models are updated, giving them a chance to do any special processing. $dirty = $this->getDirty(); if (count($dirty) > 0) { $this->setKeysForSaveQuery($query)->update($dirty); $this->syncChanges(); $this->fireModelEvent('updated', false); } return true; } /** * Set the keys for a select query. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSelectQuery($query) { $query->where($this->getKeyName(), '=', $this->getKeyForSelectQuery()); return $query; } /** * Get the primary key value for a select query. * * @return mixed */ protected function getKeyForSelectQuery() { return $this->original[$this->getKeyName()] ?? $this->getKey(); } /** * Set the keys for a save update query. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSaveQuery($query) { $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); return $query; } /** * Get the primary key value for a save query. * * @return mixed */ protected function getKeyForSaveQuery() { return $this->original[$this->getKeyName()] ?? $this->getKey(); } /** * Perform a model insert operation. * * @param \Illuminate\Database\Eloquent\Builder $query * @return bool */ protected function performInsert(Builder $query) { if ($this->usesUniqueIds()) { $this->setUniqueIds(); } if ($this->fireModelEvent('creating') === false) { return false; } // First we'll need to create a fresh query instance and touch the creation and // update timestamps on this model, which are maintained by us for developer // convenience. After, we will just continue saving these model instances. if ($this->usesTimestamps()) { $this->updateTimestamps(); } // If the model has an incrementing key, we can use the "insertGetId" method on // the query builder, which will give us back the final inserted ID for this // table from the database. Not all tables have to be incrementing though. $attributes = $this->getAttributesForInsert(); if ($this->getIncrementing()) { $this->insertAndSetId($query, $attributes); } // If the table isn't incrementing we'll simply insert these attributes as they // are. These attribute arrays must contain an "id" column previously placed // there by the developer as the manually determined key for these models. else { if (empty($attributes)) { return true; } $query->insert($attributes); } // We will go ahead and set the exists property to true, so that it is set when // the created event is fired, just in case the developer tries to update it // during the event. This will allow them to do so and run an update here. $this->exists = true; $this->wasRecentlyCreated = true; $this->fireModelEvent('created', false); return true; } /** * Insert the given attributes and set the ID on the model. * * @param \Illuminate\Database\Eloquent\Builder $query * @param array $attributes * @return void */ protected function insertAndSetId(Builder $query, $attributes) { $id = $query->insertGetId($attributes, $keyName = $this->getKeyName()); $this->setAttribute($keyName, $id); } /** * Destroy the models for the given IDs. * * @param \Illuminate\Support\Collection|array|int|string $ids * @return int */ public static function destroy($ids) { if ($ids instanceof EloquentCollection) { $ids = $ids->modelKeys(); } if ($ids instanceof BaseCollection) { $ids = $ids->all(); } $ids = is_array($ids) ? $ids : func_get_args(); if (count($ids) === 0) { return 0; } // We will actually pull the models from the database table and call delete on // each of them individually so that their events get fired properly with a // correct set of attributes in case the developers wants to check these. $key = ($instance = new static)->getKeyName(); $count = 0; foreach ($instance->whereIn($key, $ids)->get() as $model) { if ($model->delete()) { $count++; } } return $count; } /** * Delete the model from the database. * * @return bool|null * * @throws \LogicException */ public function delete() { $this->mergeAttributesFromCachedCasts(); if (is_null($this->getKeyName())) { throw new LogicException('No primary key defined on model.'); } // If the model doesn't exist, there is nothing to delete so we'll just return // immediately and not do anything else. Otherwise, we will continue with a // deletion process on the model, firing the proper events, and so forth. if (! $this->exists) { return; } if ($this->fireModelEvent('deleting') === false) { return false; } // Here, we'll touch the owning models, verifying these timestamps get updated // for the models. This will allow any caching to get broken on the parents // by the timestamp. Then we will go ahead and delete the model instance. $this->touchOwners(); $this->performDeleteOnModel(); // Once the model has been deleted, we will fire off the deleted event so that // the developers may hook into post-delete operations. We will then return // a boolean true as the delete is presumably successful on the database. $this->fireModelEvent('deleted', false); return true; } /** * Delete the model from the database without raising any events. * * @return bool */ public function deleteQuietly() { return static::withoutEvents(fn () => $this->delete()); } /** * Delete the model from the database within a transaction. * * @return bool|null * * @throws \Throwable */ public function deleteOrFail() { if (! $this->exists) { return false; } return $this->getConnection()->transaction(fn () => $this->delete()); } /** * Force a hard delete on a soft deleted model. * * This method protects developers from running forceDelete when the trait is missing. * * @return bool|null */ public function forceDelete() { return $this->delete(); } /** * Perform the actual delete query on this model instance. * * @return void */ protected function performDeleteOnModel() { $this->setKeysForSaveQuery($this->newModelQuery())->delete(); $this->exists = false; } /** * Begin querying the model. * * @return \Illuminate\Database\Eloquent\Builder */ public static function query() { return (new static)->newQuery(); } /** * Get a new query builder for the model's table. * * @return \Illuminate\Database\Eloquent\Builder */ public function newQuery() { return $this->registerGlobalScopes($this->newQueryWithoutScopes()); } /** * Get a new query builder that doesn't have any global scopes or eager loading. * * @return \Illuminate\Database\Eloquent\Builder|static */ public function newModelQuery() { return $this->newEloquentBuilder( $this->newBaseQueryBuilder() )->setModel($this); } /** * Get a new query builder with no relationships loaded. * * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryWithoutRelationships() { return $this->registerGlobalScopes($this->newModelQuery()); } /** * Register the global scopes for this builder instance. * * @param \Illuminate\Database\Eloquent\Builder $builder * @return \Illuminate\Database\Eloquent\Builder */ public function registerGlobalScopes($builder) { foreach ($this->getGlobalScopes() as $identifier => $scope) { $builder->withGlobalScope($identifier, $scope); } return $builder; } /** * Get a new query builder that doesn't have any global scopes. * * @return \Illuminate\Database\Eloquent\Builder|static */ public function newQueryWithoutScopes() { return $this->newModelQuery() ->with($this->with) ->withCount($this->withCount); } /** * Get a new query instance without a given scope. * * @param \Illuminate\Database\Eloquent\Scope|string $scope * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryWithoutScope($scope) { return $this->newQuery()->withoutGlobalScope($scope); } /** * Get a new query to restore one or more models by their queueable IDs. * * @param array|int $ids * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryForRestoration($ids) { return $this->newQueryWithoutScopes()->whereKey($ids); } /** * Create a new Eloquent query builder for the model. * * @param \Illuminate\Database\Query\Builder $query * @return \Illuminate\Database\Eloquent\Builder|static */ public function newEloquentBuilder($query) { return new Builder($query); } /** * Get a new query builder instance for the connection. * * @return \Illuminate\Database\Query\Builder */ protected function newBaseQueryBuilder() { return $this->getConnection()->query(); } /** * Create a new Eloquent Collection instance. * * @param array $models * @return \Illuminate\Database\Eloquent\Collection */ public function newCollection(array $models = []) { return new Collection($models); } /** * Create a new pivot model instance. * * @param \Illuminate\Database\Eloquent\Model $parent * @param array $attributes * @param string $table * @param bool $exists * @param string|null $using * @return \Illuminate\Database\Eloquent\Relations\Pivot */ public function newPivot(self $parent, array $attributes, $table, $exists, $using = null) { return $using ? $using::fromRawAttributes($parent, $attributes, $table, $exists) : Pivot::fromAttributes($parent, $attributes, $table, $exists); } /** * Determine if the model has a given scope. * * @param string $scope * @return bool */ public function hasNamedScope($scope) { return method_exists($this, 'scope'.ucfirst($scope)); } /** * Apply the given named scope if possible. * * @param string $scope * @param array $parameters * @return mixed */ public function callNamedScope($scope, array $parameters = []) { return $this->{'scope'.ucfirst($scope)}(...$parameters); } /** * Convert the model instance to an array. * * @return array */ public function toArray() { return array_merge($this->attributesToArray(), $this->relationsToArray()); } /** * Convert the model instance to JSON. * * @param int $options * @return string * * @throws \Illuminate\Database\Eloquent\JsonEncodingException */ public function toJson($options = 0) { $json = json_encode($this->jsonSerialize(), $options); if (json_last_error() !== JSON_ERROR_NONE) { throw JsonEncodingException::forModel($this, json_last_error_msg()); } return $json; } /** * Convert the object into something JSON serializable. * * @return mixed */ public function jsonSerialize(): mixed { return $this->toArray(); } /** * Reload a fresh model instance from the database. * * @param array|string $with * @return static|null */ public function fresh($with = []) { if (! $this->exists) { return; } return $this->setKeysForSelectQuery($this->newQueryWithoutScopes()) ->useWritePdo() ->with(is_string($with) ? func_get_args() : $with) ->first(); } /** * Reload the current model instance with fresh attributes from the database. * * @return $this */ public function refresh() { if (! $this->exists) { return $this; } $this->setRawAttributes( $this->setKeysForSelectQuery($this->newQueryWithoutScopes()) ->useWritePdo() ->firstOrFail() ->attributes ); $this->load(collect($this->relations)->reject(function ($relation) { return $relation instanceof Pivot || (is_object($relation) && in_array(AsPivot::class, class_uses_recursive($relation), true)); })->keys()->all()); $this->syncOriginal(); return $this; } /** * Clone the model into a new, non-existing instance. * * @param array|null $except * @return static */ public function replicate(array $except = null) { $defaults = array_values(array_filter([ $this->getKeyName(), $this->getCreatedAtColumn(), $this->getUpdatedAtColumn(), ...$this->uniqueIds(), ])); $attributes = Arr::except( $this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults ); return tap(new static, function ($instance) use ($attributes) { $instance->setRawAttributes($attributes); $instance->setRelations($this->relations); $instance->fireModelEvent('replicating', false); }); } /** * Clone the model into a new, non-existing instance without raising any events. * * @param array|null $except * @return static */ public function replicateQuietly(array $except = null) { return static::withoutEvents(fn () => $this->replicate($except)); } /** * Determine if two models have the same ID and belong to the same table. * * @param \Illuminate\Database\Eloquent\Model|null $model * @return bool */ public function is($model) { return ! is_null($model) && $this->getKey() === $model->getKey() && $this->getTable() === $model->getTable() && $this->getConnectionName() === $model->getConnectionName(); } /** * Determine if two models are not the same. * * @param \Illuminate\Database\Eloquent\Model|null $model * @return bool */ public function isNot($model) { return ! $this->is($model); } /** * Get the database connection for the model. * * @return \Illuminate\Database\Connection */ public function getConnection() { return static::resolveConnection($this->getConnectionName()); } /** * Get the current connection name for the model. * * @return string|null */ public function getConnectionName() { return $this->connection; } /** * Set the connection associated with the model. * * @param string|null $name * @return $this */ public function setConnection($name) { $this->connection = $name; return $this; } /** * Resolve a connection instance. * * @param string|null $connection * @return \Illuminate\Database\Connection */ public static function resolveConnection($connection = null) { return static::$resolver->connection($connection); } /** * Get the connection resolver instance. * * @return \Illuminate\Database\ConnectionResolverInterface|null */ public static function getConnectionResolver() { return static::$resolver; } /** * Set the connection resolver instance. * * @param \Illuminate\Database\ConnectionResolverInterface $resolver * @return void */ public static function setConnectionResolver(Resolver $resolver) { static::$resolver = $resolver; } /** * Unset the connection resolver for models. * * @return void */ public static function unsetConnectionResolver() { static::$resolver = null; } /** * Get the table associated with the model. * * @return string */ public function getTable() { return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this))); } /** * Set the table associated with the model. * * @param string $table * @return $this */ public function setTable($table) { $this->table = $table; return $this; } /** * Get the primary key for the model. * * @return string */ public function getKeyName() { return $this->primaryKey; } /** * Set the primary key for the model. * * @param string $key * @return $this */ public function setKeyName($key) { $this->primaryKey = $key; return $this; } /** * Get the table qualified key name. * * @return string */ public function getQualifiedKeyName() { return $this->qualifyColumn($this->getKeyName()); } /** * Get the auto-incrementing key type. * * @return string */ public function getKeyType() { return $this->keyType; } /** * Set the data type for the primary key. * * @param string $type * @return $this */ public function setKeyType($type) { $this->keyType = $type; return $this; } /** * Get the value indicating whether the IDs are incrementing. * * @return bool */ public function getIncrementing() { return $this->incrementing; } /** * Set whether IDs are incrementing. * * @param bool $value * @return $this */ public function setIncrementing($value) { $this->incrementing = $value; return $this; } /** * Get the value of the model's primary key. * * @return mixed */ public function getKey() { return $this->getAttribute($this->getKeyName()); } /** * Get the queueable identity for the entity. * * @return mixed */ public function getQueueableId() { return $this->getKey(); } /** * Get the queueable relationships for the entity. * * @return array */ public function getQueueableRelations() { $relations = []; foreach ($this->getRelations() as $key => $relation) { if (! method_exists($this, $key)) { continue; } $relations[] = $key; if ($relation instanceof QueueableCollection) { foreach ($relation->getQueueableRelations() as $collectionValue) { $relations[] = $key.'.'.$collectionValue; } } if ($relation instanceof QueueableEntity) { foreach ($relation->getQueueableRelations() as $entityValue) { $relations[] = $key.'.'.$entityValue; } } } return array_unique($relations); } /** * Get the queueable connection for the entity. * * @return string|null */ public function getQueueableConnection() { return $this->getConnectionName(); } /** * Get the value of the model's route key. * * @return mixed */ public function getRouteKey() { return $this->getAttribute($this->getRouteKeyName()); } /** * Get the route key for the model. * * @return string */ public function getRouteKeyName() { return $this->getKeyName(); } /** * Retrieve the model for a bound value. * * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveRouteBinding($value, $field = null) { return $this->resolveRouteBindingQuery($this, $value, $field)->first(); } /** * Retrieve the model for a bound value. * * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveSoftDeletableRouteBinding($value, $field = null) { return $this->resolveRouteBindingQuery($this, $value, $field)->withTrashed()->first(); } /** * Retrieve the child model for a bound value. * * @param string $childType * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveChildRouteBinding($childType, $value, $field) { return $this->resolveChildRouteBindingQuery($childType, $value, $field)->first(); } /** * Retrieve the child model for a bound value. * * @param string $childType * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveSoftDeletableChildRouteBinding($childType, $value, $field) { return $this->resolveChildRouteBindingQuery($childType, $value, $field)->withTrashed()->first(); } /** * Retrieve the child model query for a bound value. * * @param string $childType * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Relations\Relation */ protected function resolveChildRouteBindingQuery($childType, $value, $field) { $relationship = $this->{$this->childRouteBindingRelationshipName($childType)}(); $field = $field ?: $relationship->getRelated()->getRouteKeyName(); if ($relationship instanceof HasManyThrough || $relationship instanceof BelongsToMany) { $field = $relationship->getRelated()->getTable().'.'.$field; } return $relationship instanceof Model ? $relationship->resolveRouteBindingQuery($relationship, $value, $field) : $relationship->getRelated()->resolveRouteBindingQuery($relationship, $value, $field); } /** * Retrieve the child route model binding relationship name for the given child type. * * @param string $childType * @return string */ protected function childRouteBindingRelationshipName($childType) { return Str::plural(Str::camel($childType)); } /** * Retrieve the model for a bound value. * * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Relations\Relation */ public function resolveRouteBindingQuery($query, $value, $field = null) { return $query->where($field ?? $this->getRouteKeyName(), $value); } /** * Get the default foreign key name for the model. * * @return string */ public function getForeignKey() { return Str::snake(class_basename($this)).'_'.$this->getKeyName(); } /** * Get the number of models to return per page. * * @return int */ public function getPerPage() { return $this->perPage; } /** * Set the number of models to return per page. * * @param int $perPage * @return $this */ public function setPerPage($perPage) { $this->perPage = $perPage; return $this; } /** * Determine if lazy loading is disabled. * * @return bool */ public static function preventsLazyLoading() { return static::$modelsShouldPreventLazyLoading; } /** * Determine if discarding guarded attribute fills is disabled. * * @return bool */ public static function preventsSilentlyDiscardingAttributes() { return static::$modelsShouldPreventSilentlyDiscardingAttributes; } /** * Determine if accessing missing attributes is disabled. * * @return bool */ public static function preventsAccessingMissingAttributes() { return static::$modelsShouldPreventAccessingMissingAttributes; } /** * Get the broadcast channel route definition that is associated with the given entity. * * @return string */ public function broadcastChannelRoute() { return str_replace('\\', '.', get_class($this)).'.{'.Str::camel(class_basename($this)).'}'; } /** * Get the broadcast channel name that is associated with the given entity. * * @return string */ public function broadcastChannel() { return str_replace('\\', '.', get_class($this)).'.'.$this->getKey(); } /** * Dynamically retrieve attributes on the model. * * @param string $key * @return mixed */ public function __get($key) { return $this->getAttribute($key); } /** * Dynamically set attributes on the model. * * @param string $key * @param mixed $value * @return void */ public function __set($key, $value) { $this->setAttribute($key, $value); } /** * Determine if the given attribute exists. * * @param mixed $offset * @return bool */ public function offsetExists($offset): bool { try { return ! is_null($this->getAttribute($offset)); } catch (MissingAttributeException) { return false; } } /** * Get the value for a given offset. * * @param mixed $offset * @return mixed */ public function offsetGet($offset): mixed { return $this->getAttribute($offset); } /** * Set the value for a given offset. * * @param mixed $offset * @param mixed $value * @return void */ public function offsetSet($offset, $value): void { $this->setAttribute($offset, $value); } /** * Unset the value for a given offset. * * @param mixed $offset * @return void */ public function offsetUnset($offset): void { unset($this->attributes[$offset], $this->relations[$offset]); } /** * Determine if an attribute or relation exists on the model. * * @param string $key * @return bool */ public function __isset($key) { return $this->offsetExists($key); } /** * Unset an attribute on the model. * * @param string $key * @return void */ public function __unset($key) { $this->offsetUnset($key); } /** * Handle dynamic method calls into the model. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (in_array($method, ['increment', 'decrement', 'incrementQuietly', 'decrementQuietly'])) { return $this->$method(...$parameters); } if ($resolver = $this->relationResolver(static::class, $method)) { return $resolver($this); } if (Str::startsWith($method, 'through') && method_exists($this, $relationMethod = Str::of($method)->after('through')->lcfirst()->toString())) { return $this->through($relationMethod); } return $this->forwardCallTo($this->newQuery(), $method, $parameters); } /** * Handle dynamic static method calls into the model. * * @param string $method * @param array $parameters * @return mixed */ public static function __callStatic($method, $parameters) { return (new static)->$method(...$parameters); } /** * Convert the model to its string representation. * * @return string */ public function __toString() { return $this->escapeWhenCastingToString ? e($this->toJson()) : $this->toJson(); } /** * Indicate that the object's string representation should be escaped when __toString is invoked. * * @param bool $escape * @return $this */ public function escapeWhenCastingToString($escape = true) { $this->escapeWhenCastingToString = $escape; return $this; } /** * Prepare the object for serialization. * * @return array */ public function __sleep() { $this->mergeAttributesFromCachedCasts(); $this->classCastCache = []; $this->attributeCastCache = []; return array_keys(get_object_vars($this)); } /** * When a model is being unserialized, check if it needs to be booted. * * @return void */ public function __wakeup() { $this->bootIfNotBooted(); $this->initializeTraits(); } } Eloquent/ModelNotFoundException.php000075500000002613147536122140013457 0ustar00 */ protected $model; /** * The affected model IDs. * * @var array */ protected $ids; /** * Set the affected Eloquent model and instance ids. * * @param class-string $model * @param array|int|string $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 class-string */ public function getModel() { return $this->model; } /** * Get the affected Eloquent model IDs. * * @return array */ public function getIds() { return $this->ids; } } Eloquent/PendingHasThroughRelationship.php000064400000005714147536122140015030 0ustar00rootModel = $rootModel; $this->localRelationship = $localRelationship; } /** * Define the distant relationship that this model has. * * @param string|(callable(\Illuminate\Database\Eloquent\Model): (\Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Eloquent\Relations\HasMany)) $callback * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough|\Illuminate\Database\Eloquent\Relations\HasOneThrough */ public function has($callback) { if (is_string($callback)) { $callback = fn () => $this->localRelationship->getRelated()->{$callback}(); } $distantRelation = $callback($this->localRelationship->getRelated()); if ($distantRelation instanceof HasMany) { return $this->rootModel->hasManyThrough( $distantRelation->getRelated()::class, $this->localRelationship->getRelated()::class, $this->localRelationship->getForeignKeyName(), $distantRelation->getForeignKeyName(), $this->localRelationship->getLocalKeyName(), $distantRelation->getLocalKeyName(), ); } return $this->rootModel->hasOneThrough( $distantRelation->getRelated()::class, $this->localRelationship->getRelated()::class, $this->localRelationship->getForeignKeyName(), $distantRelation->getForeignKeyName(), $this->localRelationship->getLocalKeyName(), $distantRelation->getLocalKeyName(), ); } /** * Handle dynamic method calls into the model. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (Str::startsWith($method, 'has')) { return $this->has(Str::of($method)->after('has')->lcfirst()->toString()); } throw new BadMethodCallException(sprintf( 'Call to undefined method %s::%s()', static::class, $method )); } } Eloquent/Prunable.php000064400000002704147536122140010631 0ustar00prunable() ->when(in_array(SoftDeletes::class, class_uses_recursive(get_class($this))), function ($query) { $query->withTrashed(); })->chunkById($chunkSize, function ($models) use (&$total) { $models->each->prune(); $total += $models->count(); event(new ModelsPruned(static::class, $total)); }); return $total; } /** * Get the prunable model query. * * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { throw new LogicException('Please implement the prunable method on your model.'); } /** * Prune the model in the database. * * @return bool|null */ public function prune() { $this->pruning(); return in_array(SoftDeletes::class, class_uses_recursive(get_class($this))) ? $this->forceDelete() : $this->delete(); } /** * Prepare the model for pruning. * * @return void */ protected function pruning() { // } } Eloquent/QueueEntityResolver.php000064400000001245147536122140013063 0ustar00find($id); if ($instance) { return $instance; } throw new EntityNotFoundException($type, $id); } } Eloquent/RelationNotFoundException.php000075500000001751147536122140014176 0ustar00model = $class; $instance->relation = $relation; return $instance; } } Eloquent/Relations/BelongsTo.php000075500000025650147536122140012725 0ustar00ownerKey = $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->getForeignKeyFrom($this->child))) { 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->getForeignKeyFrom($this->child)); } } /** * 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->whereInEager($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 = $this->getForeignKeyFrom($model))) { $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) { // First we will get to build a dictionary of the child models by their primary // key of the relationship, then we can easily match the children back onto // the parents using that dictionary and the primary key of the children. $dictionary = []; foreach ($results as $result) { $attribute = $this->getDictionaryKey($this->getRelatedKeyFrom($result)); $dictionary[$attribute] = $result; } // Once we have the dictionary constructed, we can loop through all the parents // and match back onto their children using these keys of the dictionary and // the primary key of the children to map them onto the correct instances. foreach ($models as $model) { $attribute = $this->getDictionaryKey($this->getForeignKeyFrom($model)); if (isset($dictionary[$attribute])) { $model->setRelation($relation, $dictionary[$attribute]); } } return $models; } /** * Associate the model instance to the given parent. * * @param \Illuminate\Database\Eloquent\Model|int|string|null $model * @return \Illuminate\Database\Eloquent\Model */ public function associate($model) { $ownerKey = $model instanceof Model ? $model->getAttribute($this->ownerKey) : $model; $this->child->setAttribute($this->foreignKey, $ownerKey); if ($model instanceof Model) { $this->child->setRelation($this->relationName, $model); } else { $this->child->unsetRelation($this->relationName); } return $this->child; } /** * Dissociate previously associated model from the given parent. * * @return \Illuminate\Database\Eloquent\Model */ public function dissociate() { $this->child->setAttribute($this->foreignKey, null); return $this->child->setRelation($this->relationName, null); } /** * Alias of "dissociate" method. * * @return \Illuminate\Database\Eloquent\Model */ public function disassociate() { return $this->dissociate(); } /** * Add the constraints for a relationship query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($parentQuery->getQuery()->from == $query->getQuery()->from) { return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); } return $query->select($columns)->whereColumn( $this->getQualifiedForeignKeyName(), '=', $query->qualifyColumn($this->ownerKey) ); } /** * Add the constraints for a relationship query on the same table. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { $query->select($columns)->from( $query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash() ); $query->getModel()->setTable($hash); return $query->whereColumn( $hash.'.'.$this->ownerKey, '=', $this->getQualifiedForeignKeyName() ); } /** * Determine if the related model has an auto-incrementing ID. * * @return bool */ protected function relationHasIncrementingId() { return $this->related->getIncrementing() && in_array($this->related->getKeyType(), ['int', 'integer']); } /** * Make a new related instance for the given model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return \Illuminate\Database\Eloquent\Model */ protected function newRelatedInstanceFor(Model $parent) { return $this->related->newInstance(); } /** * Get the child of the relationship. * * @return \Illuminate\Database\Eloquent\Model */ public function getChild() { return $this->child; } /** * Get the foreign key of the relationship. * * @return string */ public function getForeignKeyName() { return $this->foreignKey; } /** * Get the fully qualified foreign key of the relationship. * * @return string */ public function getQualifiedForeignKeyName() { return $this->child->qualifyColumn($this->foreignKey); } /** * Get the key value of the child's foreign key. * * @return mixed */ public function getParentKey() { return $this->getForeignKeyFrom($this->child); } /** * Get the associated key of the relationship. * * @return string */ public function getOwnerKeyName() { return $this->ownerKey; } /** * Get the fully qualified associated key of the relationship. * * @return string */ public function getQualifiedOwnerKeyName() { return $this->related->qualifyColumn($this->ownerKey); } /** * Get the value of the model's associated key. * * @param \Illuminate\Database\Eloquent\Model $model * @return mixed */ protected function getRelatedKeyFrom(Model $model) { return $model->{$this->ownerKey}; } /** * Get the value of the model's foreign key. * * @param \Illuminate\Database\Eloquent\Model $model * @return mixed */ protected function getForeignKeyFrom(Model $model) { return $model->{$this->foreignKey}; } /** * Get the name of the relationship. * * @return string */ public function getRelationName() { return $this->relationName; } } Eloquent/Relations/BelongsToMany.php000075500000123463147536122140013553 0ustar00 $table * @param string $foreignPivotKey * @param string $relatedPivotKey * @param string $parentKey * @param string $relatedKey * @param string|null $relationName * @return void */ public function __construct(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName = null) { $this->parentKey = $parentKey; $this->relatedKey = $relatedKey; $this->relationName = $relationName; $this->relatedPivotKey = $relatedPivotKey; $this->foreignPivotKey = $foreignPivotKey; $this->table = $this->resolveTableName($table); parent::__construct($query, $parent); } /** * Attempt to resolve the intermediate table name from the given string. * * @param string $table * @return string */ protected function resolveTableName($table) { if (! str_contains($table, '\\') || ! class_exists($table)) { return $table; } $model = new $table; if (! $model instanceof Model) { return $table; } if (in_array(AsPivot::class, class_uses_recursive($model))) { $this->using($table); } return $model->getTable(); } /** * Set the base constraints on the relation query. * * @return void */ public function addConstraints() { $this->performJoin(); if (static::$constraints) { $this->addWhereConstraints(); } } /** * Set the join clause for the relation query. * * @param \Illuminate\Database\Eloquent\Builder|null $query * @return $this */ protected function performJoin($query = null) { $query = $query ?: $this->query; // We need to join to the intermediate table on the related model's primary // key column with the intermediate table's foreign key for the related // model instance. Then we can set the "where" for the parent models. $query->join( $this->table, $this->getQualifiedRelatedKeyName(), '=', $this->getQualifiedRelatedPivotKeyName() ); return $this; } /** * Set the where clause for the relation query. * * @return $this */ protected function addWhereConstraints() { $this->query->where( $this->getQualifiedForeignPivotKeyName(), '=', $this->parent->{$this->parentKey} ); return $this; } /** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { $whereIn = $this->whereInMethod($this->parent, $this->parentKey); $this->whereInEager( $whereIn, $this->getQualifiedForeignPivotKeyName(), $this->getKeys($models, $this->parentKey) ); } /** * Initialize the relation on a set of models. * * @param array $models * @param string $relation * @return array */ public function initRelation(array $models, $relation) { foreach ($models as $model) { $model->setRelation($relation, $this->related->newCollection()); } return $models; } /** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { $dictionary = $this->buildDictionary($results); // Once we have an array dictionary of child objects we can easily match the // children back to their parent using the dictionary and the keys on the // parent models. Then we should return these hydrated models back out. foreach ($models as $model) { $key = $this->getDictionaryKey($model->{$this->parentKey}); if (isset($dictionary[$key])) { $model->setRelation( $relation, $this->related->newCollection($dictionary[$key]) ); } } return $models; } /** * Build model dictionary keyed by the relation's foreign key. * * @param \Illuminate\Database\Eloquent\Collection $results * @return array */ protected function buildDictionary(Collection $results) { // First we'll build a dictionary of child models keyed by the foreign key // of the relation so that we will easily and quickly match them to the // parents without having a possibly slow inner loop for every model. $dictionary = []; foreach ($results as $result) { $value = $this->getDictionaryKey($result->{$this->accessor}->{$this->foreignPivotKey}); $dictionary[$value][] = $result; } return $dictionary; } /** * Get the class being used for pivot models. * * @return string */ public function getPivotClass() { return $this->using ?? Pivot::class; } /** * Specify the custom pivot model to use for the relationship. * * @param string $class * @return $this */ public function using($class) { $this->using = $class; return $this; } /** * Specify the custom pivot accessor to use for the relationship. * * @param string $accessor * @return $this */ public function as($accessor) { $this->accessor = $accessor; return $this; } /** * Set a where clause for a pivot table column. * * @param string $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return $this */ public function wherePivot($column, $operator = null, $value = null, $boolean = 'and') { $this->pivotWheres[] = func_get_args(); return $this->where($this->qualifyPivotColumn($column), $operator, $value, $boolean); } /** * Set a "where between" clause for a pivot table column. * * @param string $column * @param array $values * @param string $boolean * @param bool $not * @return $this */ public function wherePivotBetween($column, array $values, $boolean = 'and', $not = false) { return $this->whereBetween($this->qualifyPivotColumn($column), $values, $boolean, $not); } /** * Set a "or where between" clause for a pivot table column. * * @param string $column * @param array $values * @return $this */ public function orWherePivotBetween($column, array $values) { return $this->wherePivotBetween($column, $values, 'or'); } /** * Set a "where pivot not between" clause for a pivot table column. * * @param string $column * @param array $values * @param string $boolean * @return $this */ public function wherePivotNotBetween($column, array $values, $boolean = 'and') { return $this->wherePivotBetween($column, $values, $boolean, true); } /** * Set a "or where not between" clause for a pivot table column. * * @param string $column * @param array $values * @return $this */ public function orWherePivotNotBetween($column, array $values) { return $this->wherePivotBetween($column, $values, 'or', true); } /** * Set a "where in" clause for a pivot table column. * * @param string $column * @param mixed $values * @param string $boolean * @param bool $not * @return $this */ public function wherePivotIn($column, $values, $boolean = 'and', $not = false) { $this->pivotWhereIns[] = func_get_args(); return $this->whereIn($this->qualifyPivotColumn($column), $values, $boolean, $not); } /** * Set an "or where" clause for a pivot table column. * * @param string $column * @param mixed $operator * @param mixed $value * @return $this */ public function orWherePivot($column, $operator = null, $value = null) { return $this->wherePivot($column, $operator, $value, 'or'); } /** * Set a where clause for a pivot table column. * * In addition, new pivot records will receive this value. * * @param string|array $column * @param mixed $value * @return $this * * @throws \InvalidArgumentException */ public function withPivotValue($column, $value = null) { if (is_array($column)) { foreach ($column as $name => $value) { $this->withPivotValue($name, $value); } return $this; } if (is_null($value)) { throw new InvalidArgumentException('The provided value may not be null.'); } $this->pivotValues[] = compact('column', 'value'); return $this->wherePivot($column, '=', $value); } /** * Set an "or where in" clause for a pivot table column. * * @param string $column * @param mixed $values * @return $this */ public function orWherePivotIn($column, $values) { return $this->wherePivotIn($column, $values, 'or'); } /** * Set a "where not in" clause for a pivot table column. * * @param string $column * @param mixed $values * @param string $boolean * @return $this */ public function wherePivotNotIn($column, $values, $boolean = 'and') { return $this->wherePivotIn($column, $values, $boolean, true); } /** * Set an "or where not in" clause for a pivot table column. * * @param string $column * @param mixed $values * @return $this */ public function orWherePivotNotIn($column, $values) { return $this->wherePivotNotIn($column, $values, 'or'); } /** * Set a "where null" clause for a pivot table column. * * @param string $column * @param string $boolean * @param bool $not * @return $this */ public function wherePivotNull($column, $boolean = 'and', $not = false) { $this->pivotWhereNulls[] = func_get_args(); return $this->whereNull($this->qualifyPivotColumn($column), $boolean, $not); } /** * Set a "where not null" clause for a pivot table column. * * @param string $column * @param string $boolean * @return $this */ public function wherePivotNotNull($column, $boolean = 'and') { return $this->wherePivotNull($column, $boolean, true); } /** * Set a "or where null" clause for a pivot table column. * * @param string $column * @param bool $not * @return $this */ public function orWherePivotNull($column, $not = false) { return $this->wherePivotNull($column, 'or', $not); } /** * Set a "or where not null" clause for a pivot table column. * * @param string $column * @return $this */ public function orWherePivotNotNull($column) { return $this->orWherePivotNull($column, true); } /** * Add an "order by" clause for a pivot table column. * * @param string $column * @param string $direction * @return $this */ public function orderByPivot($column, $direction = 'asc') { return $this->orderBy($this->qualifyPivotColumn($column), $direction); } /** * Find a related model by its primary key or return a new instance of the related model. * * @param mixed $id * @param array $columns * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model */ public function findOrNew($id, $columns = ['*']) { if (is_null($instance = $this->find($id, $columns))) { $instance = $this->related->newInstance(); } return $instance; } /** * Get the first related model record matching the attributes or instantiate it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function firstOrNew(array $attributes = [], array $values = []) { if (is_null($instance = $this->related->where($attributes)->first())) { $instance = $this->related->newInstance(array_merge($attributes, $values)); } return $instance; } /** * Get the first record matching the attributes. If the record is not found, create it. * * @param array $attributes * @param array $values * @param array $joining * @param bool $touch * @return \Illuminate\Database\Eloquent\Model */ public function firstOrCreate(array $attributes = [], array $values = [], array $joining = [], $touch = true) { if (is_null($instance = (clone $this)->where($attributes)->first())) { if (is_null($instance = $this->related->where($attributes)->first())) { $instance = $this->createOrFirst($attributes, $values, $joining, $touch); } else { try { $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch)); } catch (UniqueConstraintViolationException) { // Nothing to do, the model was already attached... } } } return $instance; } /** * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record. * * @param array $attributes * @param array $values * @param array $joining * @param bool $touch * @return \Illuminate\Database\Eloquent\Model */ public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { try { return $this->getQuery()->withSavePointIfNeeded(fn () => $this->create(array_merge($attributes, $values), $joining, $touch)); } catch (UniqueConstraintViolationException $e) { // ... } try { return tap($this->related->where($attributes)->first() ?? throw $e, function ($instance) use ($joining, $touch) { $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch)); }); } catch (UniqueConstraintViolationException $e) { return (clone $this)->useWritePdo()->where($attributes)->first() ?? throw $e; } } /** * Create or update a related record matching the attributes, and fill it with values. * * @param array $attributes * @param array $values * @param array $joining * @param bool $touch * @return \Illuminate\Database\Eloquent\Model */ public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true) { return tap($this->firstOrCreate($attributes, $values, $joining, $touch), function ($instance) use ($values) { if (! $instance->wasRecentlyCreated) { $instance->fill($values); $instance->save(['touch' => false]); } }); } /** * Find a related model by its primary key. * * @param mixed $id * @param array $columns * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null */ public function find($id, $columns = ['*']) { if (! $id instanceof Model && (is_array($id) || $id instanceof Arrayable)) { return $this->findMany($id, $columns); } return $this->where( $this->getRelated()->getQualifiedKeyName(), '=', $this->parseId($id) )->first($columns); } /** * Find multiple related models by their primary keys. * * @param \Illuminate\Contracts\Support\Arrayable|array $ids * @param array $columns * @return \Illuminate\Database\Eloquent\Collection */ public function findMany($ids, $columns = ['*']) { $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids; if (empty($ids)) { return $this->getRelated()->newCollection(); } return $this->whereKey( $this->parseIds($ids) )->get($columns); } /** * Find a related model by its primary key or throw an exception. * * @param mixed $id * @param array $columns * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> */ public function findOrFail($id, $columns = ['*']) { $result = $this->find($id, $columns); $id = $id instanceof Arrayable ? $id->toArray() : $id; if (is_array($id)) { if (count($result) === count(array_unique($id))) { return $result; } } elseif (! is_null($result)) { return $result; } throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); } /** * Find a related model by its primary key or call a callback. * * @param mixed $id * @param \Closure|array $columns * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed */ public function findOr($id, $columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } $result = $this->find($id, $columns); $id = $id instanceof Arrayable ? $id->toArray() : $id; if (is_array($id)) { if (count($result) === count(array_unique($id))) { return $result; } } elseif (! is_null($result)) { return $result; } return $callback(); } /** * Add a basic where clause to the query, and return the first result. * * @param \Closure|string|array $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return \Illuminate\Database\Eloquent\Model|static */ public function firstWhere($column, $operator = null, $value = null, $boolean = 'and') { return $this->where($column, $operator, $value, $boolean)->first(); } /** * Execute the query and get the first result. * * @param array $columns * @return mixed */ public function first($columns = ['*']) { $results = $this->take(1)->get($columns); return count($results) > 0 ? $results->first() : null; } /** * Execute the query and get the first result or throw an exception. * * @param array $columns * @return \Illuminate\Database\Eloquent\Model|static * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> */ public function firstOrFail($columns = ['*']) { if (! is_null($model = $this->first($columns))) { return $model; } throw (new ModelNotFoundException)->setModel(get_class($this->related)); } /** * Execute the query and get the first result or call a callback. * * @param \Closure|array $columns * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|static|mixed */ public function firstOr($columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } if (! is_null($model = $this->first($columns))) { return $model; } return $callback(); } /** * Get the results of the relationship. * * @return mixed */ public function getResults() { return ! is_null($this->parent->{$this->parentKey}) ? $this->get() : $this->related->newCollection(); } /** * Execute the query as a "select" statement. * * @param array $columns * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { // First we'll add the proper select columns onto the query so it is run with // the proper columns. Then, we will get the results and hydrate our pivot // models with the result of those columns as a separate model relation. $builder = $this->query->applyScopes(); $columns = $builder->getQuery()->columns ? [] : $columns; $models = $builder->addSelect( $this->shouldSelect($columns) )->getModels(); $this->hydratePivotRelation($models); // If we actually found models we will also eager load any relationships that // have been specified as needing to be eager loaded. This will solve the // n + 1 query problem for the developer and also increase performance. if (count($models) > 0) { $models = $builder->eagerLoadRelations($models); } return $this->related->newCollection($models); } /** * Get the select columns for the relation query. * * @param array $columns * @return array */ protected function shouldSelect(array $columns = ['*']) { if ($columns == ['*']) { $columns = [$this->related->getTable().'.*']; } return array_merge($columns, $this->aliasedPivotColumns()); } /** * Get the pivot columns for the relation. * * "pivot_" is prefixed at each column for easy removal later. * * @return array */ protected function aliasedPivotColumns() { $defaults = [$this->foreignPivotKey, $this->relatedPivotKey]; return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) { return $this->qualifyPivotColumn($column).' as pivot_'.$column; })->unique()->all(); } /** * Get a paginator for the "select" statement. * * @param int|null $perPage * @param array $columns * @param string $pageName * @param int|null $page * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { $this->query->addSelect($this->shouldSelect($columns)); return tap($this->query->paginate($perPage, $columns, $pageName, $page), function ($paginator) { $this->hydratePivotRelation($paginator->items()); }); } /** * Paginate the given query into a simple paginator. * * @param int|null $perPage * @param array $columns * @param string $pageName * @param int|null $page * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) { $this->query->addSelect($this->shouldSelect($columns)); return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function ($paginator) { $this->hydratePivotRelation($paginator->items()); }); } /** * Paginate the given query into a cursor paginator. * * @param int|null $perPage * @param array $columns * @param string $cursorName * @param string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator */ public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { $this->query->addSelect($this->shouldSelect($columns)); return tap($this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor), function ($paginator) { $this->hydratePivotRelation($paginator->items()); }); } /** * Chunk the results of the query. * * @param int $count * @param callable $callback * @return bool */ public function chunk($count, callable $callback) { return $this->prepareQueryBuilder()->chunk($count, function ($results, $page) use ($callback) { $this->hydratePivotRelation($results->all()); return $callback($results, $page); }); } /** * Chunk the results of a query by comparing numeric IDs. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @return bool */ public function chunkById($count, callable $callback, $column = null, $alias = null) { $this->prepareQueryBuilder(); $column ??= $this->getRelated()->qualifyColumn( $this->getRelatedKeyName() ); $alias ??= $this->getRelatedKeyName(); return $this->query->chunkById($count, function ($results) use ($callback) { $this->hydratePivotRelation($results->all()); return $callback($results); }, $column, $alias); } /** * Execute a callback over each item while chunking. * * @param callable $callback * @param int $count * @return bool */ public function each(callable $callback, $count = 1000) { return $this->chunk($count, function ($results) use ($callback) { foreach ($results as $key => $value) { if ($callback($value, $key) === false) { return false; } } }); } /** * Query lazily, by chunks of the given size. * * @param int $chunkSize * @return \Illuminate\Support\LazyCollection */ public function lazy($chunkSize = 1000) { return $this->prepareQueryBuilder()->lazy($chunkSize)->map(function ($model) { $this->hydratePivotRelation([$model]); return $model; }); } /** * Query lazily, by chunking the results of a query by comparing IDs. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { $column ??= $this->getRelated()->qualifyColumn( $this->getRelatedKeyName() ); $alias ??= $this->getRelatedKeyName(); return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias)->map(function ($model) { $this->hydratePivotRelation([$model]); return $model; }); } /** * Get a lazy collection for the given query. * * @return \Illuminate\Support\LazyCollection */ public function cursor() { return $this->prepareQueryBuilder()->cursor()->map(function ($model) { $this->hydratePivotRelation([$model]); return $model; }); } /** * Prepare the query builder for query execution. * * @return \Illuminate\Database\Eloquent\Builder */ protected function prepareQueryBuilder() { return $this->query->addSelect($this->shouldSelect()); } /** * Hydrate the pivot table relationship on the models. * * @param array $models * @return void */ protected function hydratePivotRelation(array $models) { // To hydrate the pivot relationship, we will just gather the pivot attributes // and create a new Pivot model, which is basically a dynamic model that we // will set the attributes, table, and connections on it so it will work. foreach ($models as $model) { $model->setRelation($this->accessor, $this->newExistingPivot( $this->migratePivotAttributes($model) )); } } /** * Get the pivot attributes from a model. * * @param \Illuminate\Database\Eloquent\Model $model * @return array */ protected function migratePivotAttributes(Model $model) { $values = []; foreach ($model->getAttributes() as $key => $value) { // To get the pivots attributes we will just take any of the attributes which // begin with "pivot_" and add those to this arrays, as well as unsetting // them from the parent's models since they exist in a different table. if (str_starts_with($key, 'pivot_')) { $values[substr($key, 6)] = $value; unset($model->$key); } } return $values; } /** * If we're touching the parent model, touch. * * @return void */ public function touchIfTouching() { if ($this->touchingParent()) { $this->getParent()->touch(); } if ($this->getParent()->touches($this->relationName)) { $this->touch(); } } /** * Determine if we should touch the parent on sync. * * @return bool */ protected function touchingParent() { return $this->getRelated()->touches($this->guessInverseRelation()); } /** * Attempt to guess the name of the inverse of the relation. * * @return string */ protected function guessInverseRelation() { return Str::camel(Str::pluralStudly(class_basename($this->getParent()))); } /** * Touch all of the related models for the relationship. * * E.g.: Touch all roles associated with this user. * * @return void */ public function touch() { $columns = [ $this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(), ]; // If we actually have IDs for the relation, we will run the query to update all // the related model's timestamps, to make sure these all reflect the changes // to the parent models. This will help us keep any caching synced up here. if (count($ids = $this->allRelatedIds()) > 0) { $this->getRelated()->newQueryWithoutRelationships()->whereKey($ids)->update($columns); } } /** * Get all of the IDs for the related models. * * @return \Illuminate\Support\Collection */ public function allRelatedIds() { return $this->newPivotQuery()->pluck($this->relatedPivotKey); } /** * Save a new model and attach it to the parent model. * * @param \Illuminate\Database\Eloquent\Model $model * @param array $pivotAttributes * @param bool $touch * @return \Illuminate\Database\Eloquent\Model */ public function save(Model $model, array $pivotAttributes = [], $touch = true) { $model->save(['touch' => false]); $this->attach($model, $pivotAttributes, $touch); return $model; } /** * Save a new model without raising any events and attach it to the parent model. * * @param \Illuminate\Database\Eloquent\Model $model * @param array $pivotAttributes * @param bool $touch * @return \Illuminate\Database\Eloquent\Model */ public function saveQuietly(Model $model, array $pivotAttributes = [], $touch = true) { return Model::withoutEvents(function () use ($model, $pivotAttributes, $touch) { return $this->save($model, $pivotAttributes, $touch); }); } /** * Save an array of new models and attach them to the parent model. * * @param \Illuminate\Support\Collection|array $models * @param array $pivotAttributes * @return array */ public function saveMany($models, array $pivotAttributes = []) { foreach ($models as $key => $model) { $this->save($model, (array) ($pivotAttributes[$key] ?? []), false); } $this->touchIfTouching(); return $models; } /** * Save an array of new models without raising any events and attach them to the parent model. * * @param \Illuminate\Support\Collection|array $models * @param array $pivotAttributes * @return array */ public function saveManyQuietly($models, array $pivotAttributes = []) { return Model::withoutEvents(function () use ($models, $pivotAttributes) { return $this->saveMany($models, $pivotAttributes); }); } /** * Create a new instance of the related model. * * @param array $attributes * @param array $joining * @param bool $touch * @return \Illuminate\Database\Eloquent\Model */ public function create(array $attributes = [], array $joining = [], $touch = true) { $instance = $this->related->newInstance($attributes); // Once we save the related model, we need to attach it to the base model via // through intermediate table so we'll use the existing "attach" method to // accomplish this which will insert the record and any more attributes. $instance->save(['touch' => false]); $this->attach($instance, $joining, $touch); return $instance; } /** * Create an array of new instances of the related models. * * @param iterable $records * @param array $joinings * @return array */ public function createMany(iterable $records, array $joinings = []) { $instances = []; foreach ($records as $key => $record) { $instances[] = $this->create($record, (array) ($joinings[$key] ?? []), false); } $this->touchIfTouching(); return $instances; } /** * Add the constraints for a relationship query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($parentQuery->getQuery()->from == $query->getQuery()->from) { return $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns); } $this->performJoin($query); return parent::getRelationExistenceQuery($query, $parentQuery, $columns); } /** * Add the constraints for a relationship query on the same table. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfJoin(Builder $query, Builder $parentQuery, $columns = ['*']) { $query->select($columns); $query->from($this->related->getTable().' as '.$hash = $this->getRelationCountHash()); $this->related->setTable($hash); $this->performJoin($query); return parent::getRelationExistenceQuery($query, $parentQuery, $columns); } /** * Get the key for comparing against the parent key in "has" query. * * @return string */ public function getExistenceCompareKey() { return $this->getQualifiedForeignPivotKeyName(); } /** * Specify that the pivot table has creation and update timestamps. * * @param mixed $createdAt * @param mixed $updatedAt * @return $this */ public function withTimestamps($createdAt = null, $updatedAt = null) { $this->withTimestamps = true; $this->pivotCreatedAt = $createdAt; $this->pivotUpdatedAt = $updatedAt; return $this->withPivot($this->createdAt(), $this->updatedAt()); } /** * Get the name of the "created at" column. * * @return string */ public function createdAt() { return $this->pivotCreatedAt ?: $this->parent->getCreatedAtColumn(); } /** * Get the name of the "updated at" column. * * @return string */ public function updatedAt() { return $this->pivotUpdatedAt ?: $this->parent->getUpdatedAtColumn(); } /** * Get the foreign key for the relation. * * @return string */ public function getForeignPivotKeyName() { return $this->foreignPivotKey; } /** * Get the fully qualified foreign key for the relation. * * @return string */ public function getQualifiedForeignPivotKeyName() { return $this->qualifyPivotColumn($this->foreignPivotKey); } /** * Get the "related key" for the relation. * * @return string */ public function getRelatedPivotKeyName() { return $this->relatedPivotKey; } /** * Get the fully qualified "related key" for the relation. * * @return string */ public function getQualifiedRelatedPivotKeyName() { return $this->qualifyPivotColumn($this->relatedPivotKey); } /** * Get the parent key for the relationship. * * @return string */ public function getParentKeyName() { return $this->parentKey; } /** * Get the fully qualified parent key name for the relation. * * @return string */ public function getQualifiedParentKeyName() { return $this->parent->qualifyColumn($this->parentKey); } /** * Get the related key for the relationship. * * @return string */ public function getRelatedKeyName() { return $this->relatedKey; } /** * Get the fully qualified related key name for the relation. * * @return string */ public function getQualifiedRelatedKeyName() { return $this->related->qualifyColumn($this->relatedKey); } /** * Get the intermediate table for the relationship. * * @return string */ public function getTable() { return $this->table; } /** * Get the relationship name for the relationship. * * @return string */ public function getRelationName() { return $this->relationName; } /** * Get the name of the pivot accessor for this relationship. * * @return string */ public function getPivotAccessor() { return $this->accessor; } /** * Get the pivot columns for this relationship. * * @return array */ public function getPivotColumns() { return $this->pivotColumns; } /** * Qualify the given column name by the pivot table. * * @param string $column * @return string */ public function qualifyPivotColumn($column) { return str_contains($column, '.') ? $column : $this->table.'.'.$column; } } Eloquent/Relations/Concerns/AsPivot.php000064400000020761147536122140014163 0ustar00timestamps = $instance->hasTimestampAttributes($attributes); // The pivot model is a "dynamic" model since we will set the tables dynamically // for the instance. This allows it work for any intermediate tables for the // many to many relationship that are defined by this developer's classes. $instance->setConnection($parent->getConnectionName()) ->setTable($table) ->forceFill($attributes) ->syncOriginal(); // We store off the parent instance so we will access the timestamp column names // for the model, since the pivot model timestamps aren't easily configurable // from the developer's point of view. We can use the parents to get these. $instance->pivotParent = $parent; $instance->exists = $exists; return $instance; } /** * Create a new pivot model from raw values returned from a query. * * @param \Illuminate\Database\Eloquent\Model $parent * @param array $attributes * @param string $table * @param bool $exists * @return static */ public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false) { $instance = static::fromAttributes($parent, [], $table, $exists); $instance->timestamps = $instance->hasTimestampAttributes($attributes); $instance->setRawAttributes( array_merge($instance->getRawOriginal(), $attributes), $exists ); return $instance; } /** * Set the keys for a select query. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSelectQuery($query) { if (isset($this->attributes[$this->getKeyName()])) { return parent::setKeysForSelectQuery($query); } $query->where($this->foreignKey, $this->getOriginal( $this->foreignKey, $this->getAttribute($this->foreignKey) )); return $query->where($this->relatedKey, $this->getOriginal( $this->relatedKey, $this->getAttribute($this->relatedKey) )); } /** * Set the keys for a save update query. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSaveQuery($query) { return $this->setKeysForSelectQuery($query); } /** * Delete the pivot model record from the database. * * @return int */ public function delete() { if (isset($this->attributes[$this->getKeyName()])) { return (int) parent::delete(); } if ($this->fireModelEvent('deleting') === false) { return 0; } $this->touchOwners(); return tap($this->getDeleteQuery()->delete(), function () { $this->exists = false; $this->fireModelEvent('deleted', false); }); } /** * Get the query builder for a delete operation on the pivot. * * @return \Illuminate\Database\Eloquent\Builder */ protected function getDeleteQuery() { return $this->newQueryWithoutRelationships()->where([ $this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)), $this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)), ]); } /** * Get the table associated with the model. * * @return string */ public function getTable() { if (! isset($this->table)) { $this->setTable(str_replace( '\\', '', Str::snake(Str::singular(class_basename($this))) )); } return $this->table; } /** * Get the foreign key column name. * * @return string */ public function getForeignKey() { return $this->foreignKey; } /** * Get the "related key" column name. * * @return string */ public function getRelatedKey() { return $this->relatedKey; } /** * Get the "related key" column name. * * @return string */ public function getOtherKey() { return $this->getRelatedKey(); } /** * Set the key names for the pivot model instance. * * @param string $foreignKey * @param string $relatedKey * @return $this */ public function setPivotKeys($foreignKey, $relatedKey) { $this->foreignKey = $foreignKey; $this->relatedKey = $relatedKey; return $this; } /** * Determine if the pivot model or given attributes has timestamp attributes. * * @param array|null $attributes * @return bool */ public function hasTimestampAttributes($attributes = null) { return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes); } /** * Get the name of the "created at" column. * * @return string */ public function getCreatedAtColumn() { return $this->pivotParent ? $this->pivotParent->getCreatedAtColumn() : parent::getCreatedAtColumn(); } /** * Get the name of the "updated at" column. * * @return string */ public function getUpdatedAtColumn() { return $this->pivotParent ? $this->pivotParent->getUpdatedAtColumn() : parent::getUpdatedAtColumn(); } /** * Get the queueable identity for the entity. * * @return mixed */ public function getQueueableId() { if (isset($this->attributes[$this->getKeyName()])) { return $this->getKey(); } return sprintf( '%s:%s:%s:%s', $this->foreignKey, $this->getAttribute($this->foreignKey), $this->relatedKey, $this->getAttribute($this->relatedKey) ); } /** * Get a new query to restore one or more models by their queueable IDs. * * @param int[]|string[]|string $ids * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryForRestoration($ids) { if (is_array($ids)) { return $this->newQueryForCollectionRestoration($ids); } if (! str_contains($ids, ':')) { return parent::newQueryForRestoration($ids); } $segments = explode(':', $ids); return $this->newQueryWithoutScopes() ->where($segments[0], $segments[1]) ->where($segments[2], $segments[3]); } /** * Get a new query to restore multiple models by their queueable IDs. * * @param int[]|string[] $ids * @return \Illuminate\Database\Eloquent\Builder */ protected function newQueryForCollectionRestoration(array $ids) { $ids = array_values($ids); if (! str_contains($ids[0], ':')) { return parent::newQueryForRestoration($ids); } $query = $this->newQueryWithoutScopes(); foreach ($ids as $id) { $segments = explode(':', $id); $query->orWhere(function ($query) use ($segments) { return $query->where($segments[0], $segments[1]) ->where($segments[2], $segments[3]); }); } return $query; } /** * Unset all the loaded relations for the instance. * * @return $this */ public function unsetRelations() { $this->pivotParent = null; $this->relations = []; return $this; } } Eloquent/Relations/Concerns/CanBeOneOfMany.php000064400000023230147536122140015314 0ustar00isOneOfMany = true; $this->relationName = $relation ?: $this->getDefaultOneOfManyJoinAlias( $this->guessRelationship() ); $keyName = $this->query->getModel()->getKeyName(); $columns = is_string($columns = $column) ? [ $column => $aggregate, $keyName => $aggregate, ] : $column; if (! array_key_exists($keyName, $columns)) { $columns[$keyName] = 'MAX'; } if ($aggregate instanceof Closure) { $closure = $aggregate; } foreach ($columns as $column => $aggregate) { if (! in_array(strtolower($aggregate), ['min', 'max'])) { throw new InvalidArgumentException("Invalid aggregate [{$aggregate}] used within ofMany relation. Available aggregates: MIN, MAX"); } $subQuery = $this->newOneOfManySubQuery( $this->getOneOfManySubQuerySelectColumns(), array_merge([$column], $previous['columns'] ?? []), $aggregate, ); if (isset($previous)) { $this->addOneOfManyJoinSubQuery( $subQuery, $previous['subQuery'], $previous['columns'], ); } if (isset($closure)) { $closure($subQuery); } if (! isset($previous)) { $this->oneOfManySubQuery = $subQuery; } if (array_key_last($columns) == $column) { $this->addOneOfManyJoinSubQuery( $this->query, $subQuery, array_merge([$column], $previous['columns'] ?? []), ); } $previous = [ 'subQuery' => $subQuery, 'columns' => array_merge([$column], $previous['columns'] ?? []), ]; } $this->addConstraints(); $columns = $this->query->getQuery()->columns; if (is_null($columns) || $columns === ['*']) { $this->select([$this->qualifyColumn('*')]); } return $this; } /** * Indicate that the relation is the latest single result of a larger one-to-many relationship. * * @param string|array|null $column * @param string|null $relation * @return $this */ public function latestOfMany($column = 'id', $relation = null) { return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) { return [$column => 'MAX']; })->all(), 'MAX', $relation); } /** * Indicate that the relation is the oldest single result of a larger one-to-many relationship. * * @param string|array|null $column * @param string|null $relation * @return $this */ public function oldestOfMany($column = 'id', $relation = null) { return $this->ofMany(collect(Arr::wrap($column))->mapWithKeys(function ($column) { return [$column => 'MIN']; })->all(), 'MIN', $relation); } /** * Get the default alias for the one of many inner join clause. * * @param string $relation * @return string */ protected function getDefaultOneOfManyJoinAlias($relation) { return $relation == $this->query->getModel()->getTable() ? $relation.'_of_many' : $relation; } /** * Get a new query for the related model, grouping the query by the given column, often the foreign key of the relationship. * * @param string|array $groupBy * @param array|null $columns * @param string|null $aggregate * @return \Illuminate\Database\Eloquent\Builder */ protected function newOneOfManySubQuery($groupBy, $columns = null, $aggregate = null) { $subQuery = $this->query->getModel() ->newQuery() ->withoutGlobalScopes($this->removedScopes()); foreach (Arr::wrap($groupBy) as $group) { $subQuery->groupBy($this->qualifyRelatedColumn($group)); } if (! is_null($columns)) { foreach ($columns as $key => $column) { $aggregatedColumn = $subQuery->getQuery()->grammar->wrap($subQuery->qualifyColumn($column)); if ($key === 0) { $aggregatedColumn = "{$aggregate}({$aggregatedColumn})"; } else { $aggregatedColumn = "min({$aggregatedColumn})"; } $subQuery->selectRaw($aggregatedColumn.' as '.$subQuery->getQuery()->grammar->wrap($column.'_aggregate')); } } $this->addOneOfManySubQueryConstraints($subQuery, $groupBy, $columns, $aggregate); return $subQuery; } /** * Add the join subquery to the given query on the given column and the relationship's foreign key. * * @param \Illuminate\Database\Eloquent\Builder $parent * @param \Illuminate\Database\Eloquent\Builder $subQuery * @param array $on * @return void */ protected function addOneOfManyJoinSubQuery(Builder $parent, Builder $subQuery, $on) { $parent->beforeQuery(function ($parent) use ($subQuery, $on) { $subQuery->applyBeforeQueryCallbacks(); $parent->joinSub($subQuery, $this->relationName, function ($join) use ($on) { foreach ($on as $onColumn) { $join->on($this->qualifySubSelectColumn($onColumn.'_aggregate'), '=', $this->qualifyRelatedColumn($onColumn)); } $this->addOneOfManyJoinSubQueryConstraints($join, $on); }); }); } /** * Merge the relationship query joins to the given query builder. * * @param \Illuminate\Database\Eloquent\Builder $query * @return void */ protected function mergeOneOfManyJoinsTo(Builder $query) { $query->getQuery()->beforeQueryCallbacks = $this->query->getQuery()->beforeQueryCallbacks; $query->applyBeforeQueryCallbacks(); } /** * Get the query builder that will contain the relationship constraints. * * @return \Illuminate\Database\Eloquent\Builder */ protected function getRelationQuery() { return $this->isOneOfMany() ? $this->oneOfManySubQuery : $this->query; } /** * Get the one of many inner join subselect builder instance. * * @return \Illuminate\Database\Eloquent\Builder|void */ public function getOneOfManySubQuery() { return $this->oneOfManySubQuery; } /** * Get the qualified column name for the one-of-many relationship using the subselect join query's alias. * * @param string $column * @return string */ public function qualifySubSelectColumn($column) { return $this->getRelationName().'.'.last(explode('.', $column)); } /** * Qualify related column using the related table name if it is not already qualified. * * @param string $column * @return string */ protected function qualifyRelatedColumn($column) { return str_contains($column, '.') ? $column : $this->query->getModel()->getTable().'.'.$column; } /** * Guess the "hasOne" relationship's name via backtrace. * * @return string */ protected function guessRelationship() { return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function']; } /** * Determine whether the relationship is a one-of-many relationship. * * @return bool */ public function isOneOfMany() { return $this->isOneOfMany; } /** * Get the name of the relationship. * * @return string */ public function getRelationName() { return $this->relationName; } } Eloquent/Relations/Concerns/ComparesRelatedModels.php000064400000004047147536122140017013 0ustar00compareKeys($this->getParentKey(), $this->getRelatedKeyFrom($model)) && $this->related->getTable() === $model->getTable() && $this->related->getConnectionName() === $model->getConnectionName(); if ($match && $this instanceof SupportsPartialRelations && $this->isOneOfMany()) { return $this->query ->whereKey($model->getKey()) ->exists(); } return $match; } /** * Determine if the model is not the related instance of the relationship. * * @param \Illuminate\Database\Eloquent\Model|null $model * @return bool */ public function isNot($model) { return ! $this->is($model); } /** * Get the value of the parent model's key. * * @return mixed */ abstract public function getParentKey(); /** * Get the value of the model's related key. * * @param \Illuminate\Database\Eloquent\Model $model * @return mixed */ abstract protected function getRelatedKeyFrom(Model $model); /** * Compare the parent key with the related key. * * @param mixed $parentKey * @param mixed $relatedKey * @return bool */ protected function compareKeys($parentKey, $relatedKey) { if (empty($parentKey) || empty($relatedKey)) { return false; } if (is_int($parentKey) || is_int($relatedKey)) { return (int) $parentKey === (int) $relatedKey; } return $parentKey === $relatedKey; } } Eloquent/Relations/Concerns/InteractsWithDictionary.php000064400000001720147536122140017406 0ustar00__toString(); } if ($attribute instanceof UnitEnum) { return $attribute instanceof BackedEnum ? $attribute->value : $attribute->name; } throw new InvalidArgumentException('Model attribute value is an object but does not have a __toString method.'); } return $attribute; } } Eloquent/Relations/Concerns/InteractsWithPivotTable.php000064400000050516147536122140017361 0ustar00 [], '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(); $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) { $detach = array_diff($current, array_keys($records)); if (count($detach) > 0) { $this->detach($detach); $changes['detached'] = $this->castKeys($detach); } } // Now we are finally ready to attach the new records. Note that we'll disable // touching until after the entire operation is complete so we don't fire a // ton of touch operations until we are totally done syncing the records. $changes = array_merge( $changes, $this->attachNew($records, $current, false) ); // Once we have finished attaching or detaching the records, we will see if we // have done any attaching or detaching, and if we have we will touch these // relationships if they are configured to touch on any database updates. if (count($changes['attached']) || count($changes['updated']) || count($changes['detached'])) { $this->touchIfTouching(); } return $changes; } /** * Sync the intermediate tables with a list of IDs or collection of models with the given pivot values. * * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param array $values * @param bool $detaching * @return array */ public function syncWithPivotValues($ids, array $values, bool $detaching = true) { return $this->sync(collect($this->parseIds($ids))->mapWithKeys(function ($id) use ($values) { return [$id => $values]; }), $detaching); } /** * Format the sync / toggle record list so that it is keyed by ID. * * @param array $records * @return array */ protected function formatRecordsList(array $records) { return collect($records)->mapWithKeys(function ($attributes, $id) { if (! is_array($attributes)) { [$id, $attributes] = [$attributes, []]; } return [$id => $attributes]; })->all(); } /** * Attach all of the records that aren't in the given current records. * * @param array $records * @param array $current * @param bool $touch * @return array */ protected function attachNew(array $records, array $current, $touch = true) { $changes = ['attached' => [], 'updated' => []]; foreach ($records as $id => $attributes) { // If the ID is not in the list of existing pivot IDs, we will insert a new pivot // record, otherwise, we will just update this existing record on this joining // table, so that the developers will easily update these records pain free. if (! in_array($id, $current)) { $this->attach($id, $attributes, $touch); $changes['attached'][] = $this->castKey($id); } // Now we'll try to update an existing pivot record with the attributes that were // given to the method. If the model is actually updated we will add it to the // list of updated pivot records so we return them back out to the consumer. elseif (count($attributes) > 0 && $this->updateExistingPivot($id, $attributes, $touch)) { $changes['updated'][] = $this->castKey($id); } } return $changes; } /** * Update an existing pivot record on the table. * * @param mixed $id * @param array $attributes * @param bool $touch * @return int */ public function updateExistingPivot($id, array $attributes, $touch = true) { if ($this->using && empty($this->pivotWheres) && empty($this->pivotWhereIns) && empty($this->pivotWhereNulls)) { return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch); } if ($this->hasPivotColumn($this->updatedAt())) { $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 = $pivotModel->fromDateTime($fresh); } if (! $exists && $this->hasPivotColumn($this->createdAt())) { $record[$this->createdAt()] = $fresh; } if ($this->hasPivotColumn($this->updatedAt())) { $record[$this->updatedAt()] = $fresh; } return $record; } /** * Determine whether the given column is defined as a pivot column. * * @param string $column * @return bool */ public function hasPivotColumn($column) { return in_array($column, $this->pivotColumns); } /** * Detach models from the relationship. * * @param mixed $ids * @param bool $touch * @return int */ public function detach($ids = null, $touch = true) { if ($this->using && ! empty($ids) && empty($this->pivotWheres) && empty($this->pivotWhereIns) && empty($this->pivotWhereNulls)) { $results = $this->detachUsingCustomClass($ids); } else { $query = $this->newPivotQuery(); // If associated IDs were passed to the method we will only delete those // associations, otherwise all of the association ties will be broken. // We'll return the numbers of affected rows when we do the deletes. if (! is_null($ids)) { $ids = $this->parseIds($ids); if (empty($ids)) { return 0; } $query->whereIn($this->getQualifiedRelatedPivotKeyName(), (array) $ids); } // Once we have all of the conditions set on the statement, we are ready // to run the delete on the pivot table. Then, if the touch parameter // is true, we will go ahead and touch all related models to sync. $results = $query->delete(); } if ($touch) { $this->touchIfTouching(); } return $results; } /** * Detach models from the relationship using a custom class. * * @param mixed $ids * @return int */ protected function detachUsingCustomClass($ids) { $results = 0; foreach ($this->parseIds($ids) as $id) { $results += $this->newPivot([ $this->foreignPivotKey => $this->parent->{$this->parentKey}, $this->relatedPivotKey => $id, ], true)->delete(); } return $results; } /** * Get the pivot models that are currently attached. * * @return \Illuminate\Support\Collection */ protected function getCurrentlyAttachedPivots() { return $this->newPivotQuery()->get()->map(function ($record) { $class = $this->using ?: Pivot::class; $pivot = $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true); return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey); }); } /** * Create a new pivot model instance. * * @param array $attributes * @param bool $exists * @return \Illuminate\Database\Eloquent\Relations\Pivot */ public function newPivot(array $attributes = [], $exists = false) { $attributes = array_merge(array_column($this->pivotValues, 'value', 'column'), $attributes); $pivot = $this->related->newPivot( $this->parent, $attributes, $this->table, $exists, $this->using ); return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey); } /** * Create a new existing pivot model instance. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Relations\Pivot */ public function newExistingPivot(array $attributes = []) { return $this->newPivot($attributes, true); } /** * Get a new plain query builder for the pivot table. * * @return \Illuminate\Database\Query\Builder */ public function newPivotStatement() { return $this->query->getQuery()->newQuery()->from($this->table); } /** * Get a new pivot statement for a given "other" ID. * * @param mixed $id * @return \Illuminate\Database\Query\Builder */ public function newPivotStatementForId($id) { return $this->newPivotQuery()->whereIn($this->relatedPivotKey, $this->parseIds($id)); } /** * Create a new query builder for the pivot table. * * @return \Illuminate\Database\Query\Builder */ public function newPivotQuery() { $query = $this->newPivotStatement(); foreach ($this->pivotWheres as $arguments) { $query->where(...$arguments); } foreach ($this->pivotWhereIns as $arguments) { $query->whereIn(...$arguments); } foreach ($this->pivotWhereNulls as $arguments) { $query->whereNull(...$arguments); } return $query->where($this->getQualifiedForeignPivotKeyName(), $this->parent->{$this->parentKey}); } /** * Set the columns on the pivot table to retrieve. * * @param array|mixed $columns * @return $this */ public function withPivot($columns) { $this->pivotColumns = array_merge( $this->pivotColumns, is_array($columns) ? $columns : func_get_args() ); return $this; } /** * Get all of the IDs from the given mixed value. * * @param mixed $value * @return array */ protected function parseIds($value) { if ($value instanceof Model) { return [$value->{$this->relatedKey}]; } if ($value instanceof Collection) { return $value->pluck($this->relatedKey)->all(); } if ($value instanceof BaseCollection) { return $value->toArray(); } return (array) $value; } /** * Get the ID from the given mixed value. * * @param mixed $value * @return mixed */ protected function parseId($value) { return $value instanceof Model ? $value->{$this->relatedKey} : $value; } /** * Cast the given keys to integers if they are numeric and string otherwise. * * @param array $keys * @return array */ protected function castKeys(array $keys) { return array_map(function ($v) { return $this->castKey($v); }, $keys); } /** * Cast the given key to convert to primary key type. * * @param mixed $key * @return mixed */ protected function castKey($key) { return $this->getTypeSwapValue( $this->related->getKeyType(), $key ); } /** * Cast the given pivot attributes. * * @param array $attributes * @return array */ protected function castAttributes($attributes) { return $this->using ? $this->newPivot()->fill($attributes)->getAttributes() : $attributes; } /** * Converts a given value to a given type value. * * @param string $type * @param mixed $value * @return mixed */ protected function getTypeSwapValue($type, $value) { return match (strtolower($type)) { 'int', 'integer' => (int) $value, 'real', 'float', 'double' => (float) $value, 'string' => (string) $value, default => $value, }; } } Eloquent/Relations/Concerns/SupportsDefaultModels.php000064400000003021147536122140017074 0ustar00withDefault = $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; } } Eloquent/Relations/HasMany.php000075500000003011147536122140012354 0ustar00 new HasOne( $this->getQuery(), $this->parent, $this->foreignKey, $this->localKey )); } /** * 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); } } Eloquent/Relations/HasManyThrough.php000064400000060365147536122140013731 0ustar00localKey = $localKey; $this->firstKey = $firstKey; $this->secondKey = $secondKey; $this->farParent = $farParent; $this->throughParent = $throughParent; $this->secondLocalKey = $secondLocalKey; parent::__construct($query, $throughParent); } /** * Convert the relationship to a "has one through" relationship. * * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough */ public function one() { return HasOneThrough::noConstraints(fn () => new HasOneThrough( $this->getQuery(), $this->farParent, $this->throughParent, $this->getFirstKeyName(), $this->secondKey, $this->getLocalKeyName(), $this->getSecondLocalKeyName(), )); } /** * 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->whereInEager( $whereIn, $this->getQualifiedFirstKeyName(), $this->getKeys($models, $this->localKey) ); } /** * Initialize the relation on a set of models. * * @param array $models * @param string $relation * @return array */ public function initRelation(array $models, $relation) { foreach ($models as $model) { $model->setRelation($relation, $this->related->newCollection()); } return $models; } /** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { $dictionary = $this->buildDictionary($results); // Once we have the dictionary we can simply spin through the parent models to // link them up with their children using the keyed dictionary to make the // matching very convenient and easy work. Then we'll just return them. foreach ($models as $model) { if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) { $model->setRelation( $relation, $this->related->newCollection($dictionary[$key]) ); } } return $models; } /** * Build model dictionary keyed by the relation's foreign key. * * @param \Illuminate\Database\Eloquent\Collection $results * @return array */ protected function buildDictionary(Collection $results) { $dictionary = []; // First we will create a dictionary of models keyed by the foreign key of the // relationship as this will allow us to quickly access all of the related // models without having to do nested looping which will be quite slow. foreach ($results as $result) { $dictionary[$result->laravel_through_key][] = $result; } return $dictionary; } /** * Get the first related model record matching the attributes or instantiate it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function firstOrNew(array $attributes = [], array $values = []) { if (! is_null($instance = $this->where($attributes)->first())) { return $instance; } return $this->related->newInstance(array_merge($attributes, $values)); } /** * Get the first record matching the attributes. If the record is not found, create it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function firstOrCreate(array $attributes = [], array $values = []) { if (! is_null($instance = (clone $this)->where($attributes)->first())) { return $instance; } return $this->createOrFirst(array_merge($attributes, $values)); } /** * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function createOrFirst(array $attributes = [], array $values = []) { try { return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first() ?? throw $exception; } } /** * 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->firstOrCreate($attributes, $values), function ($instance) use ($values) { if (! $instance->wasRecentlyCreated) { $instance->fill($values)->save(); } }); } /** * 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<\Illuminate\Database\Eloquent\Model> */ public function firstOrFail($columns = ['*']) { if (! is_null($model = $this->first($columns))) { return $model; } throw (new ModelNotFoundException)->setModel(get_class($this->related)); } /** * Execute the query and get the first result or call a callback. * * @param \Closure|array $columns * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|static|mixed */ public function firstOr($columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } if (! is_null($model = $this->first($columns))) { return $model; } return $callback(); } /** * Find a related model by its primary key. * * @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<\Illuminate\Database\Eloquent\Model> */ public function findOrFail($id, $columns = ['*']) { $result = $this->find($id, $columns); $id = $id instanceof Arrayable ? $id->toArray() : $id; if (is_array($id)) { if (count($result) === count(array_unique($id))) { return $result; } } elseif (! is_null($result)) { return $result; } throw (new ModelNotFoundException)->setModel(get_class($this->related), $id); } /** * Find a related model by its primary key or call a callback. * * @param mixed $id * @param \Closure|array $columns * @param \Closure|null $callback * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed */ public function findOr($id, $columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } $result = $this->find($id, $columns); $id = $id instanceof Arrayable ? $id->toArray() : $id; if (is_array($id)) { if (count($result) === count(array_unique($id))) { return $result; } } elseif (! is_null($result)) { return $result; } return $callback(); } /** * 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); } /** * Paginate the given query into a cursor paginator. * * @param int|null $perPage * @param array $columns * @param string $cursorName * @param string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator */ public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { $this->query->addSelect($this->shouldSelect($columns)); return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor); } /** * Set the select clause for the relation query. * * @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 ??= $this->getRelated()->getQualifiedKeyName(); $alias ??= $this->getRelated()->getKeyName(); return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias); } /** * 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) { $column = $column ?? $this->getRelated()->getQualifiedKeyName(); $alias = $alias ?? $this->getRelated()->getKeyName(); return $this->prepareQueryBuilder()->eachById($callback, $count, $column, $alias); } /** * Get a generator for the given query. * * @return \Illuminate\Support\LazyCollection */ public function cursor() { return $this->prepareQueryBuilder()->cursor(); } /** * Execute a callback over each item while chunking. * * @param callable $callback * @param int $count * @return bool */ public function each(callable $callback, $count = 1000) { return $this->chunk($count, function ($results) use ($callback) { foreach ($results as $key => $value) { if ($callback($value, $key) === false) { return false; } } }); } /** * Query lazily, by chunks of the given size. * * @param int $chunkSize * @return \Illuminate\Support\LazyCollection */ public function lazy($chunkSize = 1000) { return $this->prepareQueryBuilder()->lazy($chunkSize); } /** * Query lazily, by chunking the results of a query by comparing IDs. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { $column ??= $this->getRelated()->getQualifiedKeyName(); $alias ??= $this->getRelated()->getKeyName(); return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias); } /** * Prepare the query builder for query execution. * * @param array $columns * @return \Illuminate\Database\Eloquent\Builder */ protected function prepareQueryBuilder($columns = ['*']) { $builder = $this->query->applyScopes(); return $builder->addSelect( $this->shouldSelect($builder->getQuery()->columns ? [] : $columns) ); } /** * Add the constraints for a relationship query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($parentQuery->getQuery()->from === $query->getQuery()->from) { return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); } if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) { return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns); } $this->performJoin($query); return $query->select($columns)->whereColumn( $this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName() ); } /** * Add the constraints for a relationship query on the same table. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey); if ($this->throughParentSoftDeletes()) { $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); } $query->getModel()->setTable($hash); return $query->select($columns)->whereColumn( $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName() ); } /** * Add the constraints for a relationship query on the same table as the through parent. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { $table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash(); $query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName()); if ($this->throughParentSoftDeletes()) { $query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn()); } return $query->select($columns)->whereColumn( $parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey ); } /** * Get the qualified foreign key on the related model. * * @return string */ public function getQualifiedFarKeyName() { return $this->getQualifiedForeignKeyName(); } /** * Get the foreign key on the "through" model. * * @return string */ public function getFirstKeyName() { return $this->firstKey; } /** * Get the qualified foreign key on the "through" model. * * @return string */ public function getQualifiedFirstKeyName() { return $this->throughParent->qualifyColumn($this->firstKey); } /** * Get the foreign key on the related model. * * @return string */ public function getForeignKeyName() { return $this->secondKey; } /** * Get the qualified foreign key on the related model. * * @return string */ public function getQualifiedForeignKeyName() { return $this->related->qualifyColumn($this->secondKey); } /** * Get the local key on the far parent model. * * @return string */ public function getLocalKeyName() { return $this->localKey; } /** * Get the qualified local key on the far parent model. * * @return string */ public function getQualifiedLocalKeyName() { return $this->farParent->qualifyColumn($this->localKey); } /** * Get the local key on the intermediary model. * * @return string */ public function getSecondLocalKeyName() { return $this->secondLocalKey; } } Eloquent/Relations/HasOne.php000075500000010056147536122140012200 0ustar00getParentKey())) { return $this->getDefaultFor($this->parent); } return $this->query->first() ?: $this->getDefaultFor($this->parent); } /** * Initialize the relation on a set of models. * * @param array $models * @param string $relation * @return array */ public function initRelation(array $models, $relation) { foreach ($models as $model) { $model->setRelation($relation, $this->getDefaultFor($model)); } return $models; } /** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { return $this->matchOne($models, $results, $relation); } /** * Add the constraints for an internal relationship existence query. * * Essentially, these queries compare on column names like "whereColumn". * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($this->isOneOfMany()) { $this->mergeOneOfManyJoinsTo($query); } return parent::getRelationExistenceQuery($query, $parentQuery, $columns); } /** * Add constraints for inner join subselect for one of many relationships. * * @param \Illuminate\Database\Eloquent\Builder $query * @param string|null $column * @param string|null $aggregate * @return void */ public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null) { $query->addSelect($this->foreignKey); } /** * Get the columns that should be selected by the one of many subquery. * * @return array|string */ public function getOneOfManySubQuerySelectColumns() { return $this->foreignKey; } /** * Add join query constraints for one of many relationships. * * @param \Illuminate\Database\Query\JoinClause $join * @return void */ public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) { $join->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey)); } /** * Make a new related instance for the given model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return \Illuminate\Database\Eloquent\Model */ public function newRelatedInstanceFor(Model $parent) { return $this->related->newInstance()->setAttribute( $this->getForeignKeyName(), $parent->{$this->localKey} ); } /** * Get the value of the model's foreign key. * * @param \Illuminate\Database\Eloquent\Model $model * @return mixed */ protected function getRelatedKeyFrom(Model $model) { return $model->getAttribute($this->getForeignKeyName()); } } Eloquent/Relations/HasOneOrMany.php000075500000035155147536122140013335 0ustar00localKey = $localKey; $this->foreignKey = $foreignKey; parent::__construct($query, $parent); } /** * Create and return an un-saved instance of the related model. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function make(array $attributes = []) { return tap($this->related->newInstance($attributes), function ($instance) { $this->setForeignAttributesForCreate($instance); }); } /** * Create and return an un-saved instance of the related models. * * @param iterable $records * @return \Illuminate\Database\Eloquent\Collection */ public function makeMany($records) { $instances = $this->related->newCollection(); foreach ($records as $record) { $instances->push($this->make($record)); } return $instances; } /** * Set the base constraints on the relation query. * * @return void */ public function addConstraints() { if (static::$constraints) { $query = $this->getRelationQuery(); $query->where($this->foreignKey, '=', $this->getParentKey()); $query->whereNotNull($this->foreignKey); } } /** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { $whereIn = $this->whereInMethod($this->parent, $this->localKey); $this->whereInEager( $whereIn, $this->foreignKey, $this->getKeys($models, $this->localKey), $this->getRelationQuery() ); } /** * Match the eagerly loaded results to their single parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function matchOne(array $models, Collection $results, $relation) { return $this->matchOneOrMany($models, $results, $relation, 'one'); } /** * Match the eagerly loaded results to their many parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function matchMany(array $models, Collection $results, $relation) { return $this->matchOneOrMany($models, $results, $relation, 'many'); } /** * Match the eagerly loaded results to their many parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @param string $type * @return array */ protected function matchOneOrMany(array $models, Collection $results, $relation, $type) { $dictionary = $this->buildDictionary($results); // Once we have the dictionary we can simply spin through the parent models to // link them up with their children using the keyed dictionary to make the // matching very convenient and easy work. Then we'll just return them. foreach ($models as $model) { if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) { $model->setRelation( $relation, $this->getRelationValue($dictionary, $key, $type) ); } } return $models; } /** * Get the value of a relationship by one or many type. * * @param array $dictionary * @param string $key * @param string $type * @return mixed */ protected function getRelationValue(array $dictionary, $key, $type) { $value = $dictionary[$key]; return $type === 'one' ? reset($value) : $this->related->newCollection($value); } /** * Build model dictionary keyed by the relation's foreign key. * * @param \Illuminate\Database\Eloquent\Collection $results * @return array */ protected function buildDictionary(Collection $results) { $foreign = $this->getForeignKeyName(); return $results->mapToDictionary(function ($result) use ($foreign) { return [$this->getDictionaryKey($result->{$foreign}) => $result]; })->all(); } /** * Find a model by its primary key or return a new instance of the related model. * * @param mixed $id * @param array $columns * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model */ public function findOrNew($id, $columns = ['*']) { if (is_null($instance = $this->find($id, $columns))) { $instance = $this->related->newInstance(); $this->setForeignAttributesForCreate($instance); } return $instance; } /** * Get the first related model record matching the attributes or instantiate it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function firstOrNew(array $attributes = [], array $values = []) { if (is_null($instance = $this->where($attributes)->first())) { $instance = $this->related->newInstance(array_merge($attributes, $values)); $this->setForeignAttributesForCreate($instance); } return $instance; } /** * Get the first record matching the attributes. If the record is not found, create it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function firstOrCreate(array $attributes = [], array $values = []) { if (is_null($instance = (clone $this)->where($attributes)->first())) { $instance = $this->createOrFirst($attributes, $values); } return $instance; } /** * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */ public function createOrFirst(array $attributes = [], array $values = []) { try { return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); } catch (UniqueConstraintViolationException $e) { return $this->useWritePdo()->where($attributes)->first() ?? throw $e; } } /** * 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->firstOrCreate($attributes, $values), function ($instance) use ($values) { if (! $instance->wasRecentlyCreated) { $instance->fill($values)->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 model instance without raising any events to the parent model. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Database\Eloquent\Model|false */ public function saveQuietly(Model $model) { return Model::withoutEvents(function () use ($model) { return $this->save($model); }); } /** * 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; } /** * Attach a collection of models to the parent instance without raising any events to the parent model. * * @param iterable $models * @return iterable */ public function saveManyQuietly($models) { return Model::withoutEvents(function () use ($models) { return $this->saveMany($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 new instance of the related model without raising any events to the parent model. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function createQuietly(array $attributes = []) { return Model::withoutEvents(fn () => $this->create($attributes)); } /** * Create a new instance of the related model. Allow mass-assignment. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function forceCreate(array $attributes = []) { $attributes[$this->getForeignKeyName()] = $this->getParentKey(); return $this->related->forceCreate($attributes); } /** * Create a new instance of the related model with mass assignment without raising model events. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function forceCreateQuietly(array $attributes = []) { return Model::withoutEvents(fn () => $this->forceCreate($attributes)); } /** * 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; } /** * Create a Collection of new instances of the related model without raising any events to the parent model. * * @param iterable $records * @return \Illuminate\Database\Eloquent\Collection */ public function createManyQuietly(iterable $records) { return Model::withoutEvents(fn () => $this->createMany($records)); } /** * Set the foreign ID for creating a related model. * * @param \Illuminate\Database\Eloquent\Model $model * @return void */ protected function setForeignAttributesForCreate(Model $model) { $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); } /** * Add the constraints for a relationship query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($query->getQuery()->from == $parentQuery->getQuery()->from) { return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); } return parent::getRelationExistenceQuery($query, $parentQuery, $columns); } /** * Add the constraints for a relationship query on the same table. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) { $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); $query->getModel()->setTable($hash); return $query->select($columns)->whereColumn( $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->getForeignKeyName() ); } /** * Get the key for comparing against the parent key in "has" query. * * @return string */ public function getExistenceCompareKey() { return $this->getQualifiedForeignKeyName(); } /** * Get the key value of the parent's local key. * * @return mixed */ public function getParentKey() { return $this->parent->getAttribute($this->localKey); } /** * Get the fully qualified parent key name. * * @return string */ public function getQualifiedParentKeyName() { return $this->parent->qualifyColumn($this->localKey); } /** * Get the plain foreign key. * * @return string */ public function getForeignKeyName() { $segments = explode('.', $this->getQualifiedForeignKeyName()); return end($segments); } /** * Get the foreign key for the relationship. * * @return string */ public function getQualifiedForeignKeyName() { return $this->foreignKey; } /** * Get the local key for the relationship. * * @return string */ public function getLocalKeyName() { return $this->localKey; } } Eloquent/Relations/HasOneThrough.php000064400000004316147536122140013540 0ustar00first() ?: $this->getDefaultFor($this->farParent); } /** * Initialize the relation on a set of models. * * @param array $models * @param string $relation * @return array */ public function initRelation(array $models, $relation) { foreach ($models as $model) { $model->setRelation($relation, $this->getDefaultFor($model)); } return $models; } /** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { $dictionary = $this->buildDictionary($results); // Once we have the dictionary we can simply spin through the parent models to // link them up with their children using the keyed dictionary to make the // matching very convenient and easy work. Then we'll just return them. foreach ($models as $model) { if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) { $value = $dictionary[$key]; $model->setRelation( $relation, reset($value) ); } } return $models; } /** * Make a new related instance for the given model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return \Illuminate\Database\Eloquent\Model */ public function newRelatedInstanceFor(Model $parent) { return $this->related->newInstance(); } } Eloquent/Relations/MorphMany.php000075500000004471147536122140012741 0ustar00 new MorphOne( $this->getQuery(), $this->getParent(), $this->morphType, $this->foreignKey, $this->localKey )); } /** * 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); } /** * Create a new instance of the related model. Allow mass-assignment. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function forceCreate(array $attributes = []) { $attributes[$this->getMorphType()] = $this->morphClass; return parent::forceCreate($attributes); } /** * Create a new instance of the related model with mass assignment without raising model events. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function forceCreateQuietly(array $attributes = []) { return Model::withoutEvents(fn () => $this->forceCreate($attributes)); } } Eloquent/Relations/MorphOne.php000075500000010254147536122140012552 0ustar00getParentKey())) { return $this->getDefaultFor($this->parent); } return $this->query->first() ?: $this->getDefaultFor($this->parent); } /** * Initialize the relation on a set of models. * * @param array $models * @param string $relation * @return array */ public function initRelation(array $models, $relation) { foreach ($models as $model) { $model->setRelation($relation, $this->getDefaultFor($model)); } return $models; } /** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { return $this->matchOne($models, $results, $relation); } /** * Get the relationship query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { if ($this->isOneOfMany()) { $this->mergeOneOfManyJoinsTo($query); } return parent::getRelationExistenceQuery($query, $parentQuery, $columns); } /** * Add constraints for inner join subselect for one of many relationships. * * @param \Illuminate\Database\Eloquent\Builder $query * @param string|null $column * @param string|null $aggregate * @return void */ public function addOneOfManySubQueryConstraints(Builder $query, $column = null, $aggregate = null) { $query->addSelect($this->foreignKey, $this->morphType); } /** * Get the columns that should be selected by the one of many subquery. * * @return array|string */ public function getOneOfManySubQuerySelectColumns() { return [$this->foreignKey, $this->morphType]; } /** * Add join query constraints for one of many relationships. * * @param \Illuminate\Database\Query\JoinClause $join * @return void */ public function addOneOfManyJoinSubQueryConstraints(JoinClause $join) { $join ->on($this->qualifySubSelectColumn($this->morphType), '=', $this->qualifyRelatedColumn($this->morphType)) ->on($this->qualifySubSelectColumn($this->foreignKey), '=', $this->qualifyRelatedColumn($this->foreignKey)); } /** * Make a new related instance for the given model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return \Illuminate\Database\Eloquent\Model */ public function newRelatedInstanceFor(Model $parent) { return $this->related->newInstance() ->setAttribute($this->getForeignKeyName(), $parent->{$this->localKey}) ->setAttribute($this->getMorphType(), $this->morphClass); } /** * Get the value of the model's foreign key. * * @param \Illuminate\Database\Eloquent\Model $model * @return mixed */ protected function getRelatedKeyFrom(Model $model) { return $model->getAttribute($this->getForeignKeyName()); } } Eloquent/Relations/MorphOneOrMany.php000075500000006775147536122140013715 0ustar00morphType = $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) { $this->getRelationQuery()->where($this->morphType, $this->morphClass); parent::addConstraints(); } } /** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { parent::addEagerConstraints($models); $this->getRelationQuery()->where($this->morphType, $this->morphClass); } /** * Create a new instance of the related model. Allow mass-assignment. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function forceCreate(array $attributes = []) { $attributes[$this->getForeignKeyName()] = $this->getParentKey(); $attributes[$this->getMorphType()] = $this->morphClass; return $this->related->forceCreate($attributes); } /** * 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; } } Eloquent/Relations/MorphPivot.php000064400000010676147536122140013137 0ustar00where($this->morphType, $this->morphClass); return parent::setKeysForSaveQuery($query); } /** * Set the keys for a select query. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSelectQuery($query) { $query->where($this->morphType, $this->morphClass); return parent::setKeysForSelectQuery($query); } /** * Delete the pivot model record from the database. * * @return int */ public function delete() { if (isset($this->attributes[$this->getKeyName()])) { return (int) parent::delete(); } if ($this->fireModelEvent('deleting') === false) { return 0; } $query = $this->getDeleteQuery(); $query->where($this->morphType, $this->morphClass); return tap($query->delete(), function () { $this->fireModelEvent('deleted', false); }); } /** * Get the morph type for the pivot. * * @return string */ public function getMorphType() { return $this->morphType; } /** * Set the morph type for the pivot. * * @param string $morphType * @return $this */ public function setMorphType($morphType) { $this->morphType = $morphType; return $this; } /** * Set the morph class for the pivot. * * @param string $morphClass * @return \Illuminate\Database\Eloquent\Relations\MorphPivot */ public function setMorphClass($morphClass) { $this->morphClass = $morphClass; return $this; } /** * Get the queueable identity for the entity. * * @return mixed */ public function getQueueableId() { if (isset($this->attributes[$this->getKeyName()])) { return $this->getKey(); } return sprintf( '%s:%s:%s:%s:%s:%s', $this->foreignKey, $this->getAttribute($this->foreignKey), $this->relatedKey, $this->getAttribute($this->relatedKey), $this->morphType, $this->morphClass ); } /** * Get a new query to restore one or more models by their queueable IDs. * * @param array|int $ids * @return \Illuminate\Database\Eloquent\Builder */ public function newQueryForRestoration($ids) { if (is_array($ids)) { return $this->newQueryForCollectionRestoration($ids); } if (! str_contains($ids, ':')) { return parent::newQueryForRestoration($ids); } $segments = explode(':', $ids); return $this->newQueryWithoutScopes() ->where($segments[0], $segments[1]) ->where($segments[2], $segments[3]) ->where($segments[4], $segments[5]); } /** * Get a new query to restore multiple models by their queueable IDs. * * @param array $ids * @return \Illuminate\Database\Eloquent\Builder */ protected function newQueryForCollectionRestoration(array $ids) { $ids = array_values($ids); if (! str_contains($ids[0], ':')) { return parent::newQueryForRestoration($ids); } $query = $this->newQueryWithoutScopes(); foreach ($ids as $id) { $segments = explode(':', $id); $query->orWhere(function ($query) use ($segments) { return $query->where($segments[0], $segments[1]) ->where($segments[2], $segments[3]) ->where($segments[4], $segments[5]); }); } return $query; } } Eloquent/Relations/MorphTo.php000064400000030134147536122140012407 0ustar00morphType = $type; parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation); } /** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { $this->buildDictionary($this->models = Collection::make($models)); } /** * Build a dictionary with the models. * * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ protected function buildDictionary(Collection $models) { foreach ($models as $model) { if ($model->{$this->morphType}) { $morphTypeKey = $this->getDictionaryKey($model->{$this->morphType}); $foreignKeyKey = $this->getDictionaryKey($model->{$this->foreignKey}); $this->dictionary[$morphTypeKey][$foreignKeyKey][] = $model; } } } /** * Get the results of the relationship. * * Called via eager load method of Eloquent query builder. * * @return mixed */ public function getEager() { foreach (array_keys($this->dictionary) as $type) { $this->matchToMorphParents($type, $this->getResultsByType($type)); } return $this->models; } /** * Get all of the relation results for a type. * * @param string $type * @return \Illuminate\Database\Eloquent\Collection */ protected function getResultsByType($type) { $instance = $this->createModelByType($type); $ownerKey = $this->ownerKey ?? $instance->getKeyName(); $query = $this->replayMacros($instance->newQuery()) ->mergeConstraintsFrom($this->getQuery()) ->with(array_merge( $this->getQuery()->getEagerLoads(), (array) ($this->morphableEagerLoads[get_class($instance)] ?? []) )) ->withCount( (array) ($this->morphableEagerLoadCounts[get_class($instance)] ?? []) ); if ($callback = ($this->morphableConstraints[get_class($instance)] ?? null)) { $callback($query); } $whereIn = $this->whereInMethod($instance, $ownerKey); return $query->{$whereIn}( $instance->getTable().'.'.$ownerKey, $this->gatherKeysByType($type, $instance->getKeyType()) )->get(); } /** * Gather all of the foreign keys for a given type. * * @param string $type * @param string $keyType * @return array */ protected function gatherKeysByType($type, $keyType) { return $keyType !== 'string' ? array_keys($this->dictionary[$type]) : array_map(function ($modelId) { return (string) $modelId; }, array_filter(array_keys($this->dictionary[$type]))); } /** * Create a new model instance by type. * * @param string $type * @return \Illuminate\Database\Eloquent\Model */ public function createModelByType($type) { $class = Model::getActualClassNameForMorph($type); return tap(new $class, function ($instance) { if (! $instance->getConnectionName()) { $instance->setConnection($this->getConnection()->getName()); } }); } /** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { return $models; } /** * Match the results for a given type to their parents. * * @param string $type * @param \Illuminate\Database\Eloquent\Collection $results * @return void */ protected function matchToMorphParents($type, Collection $results) { foreach ($results as $result) { $ownerKey = ! is_null($this->ownerKey) ? $this->getDictionaryKey($result->{$this->ownerKey}) : $result->getKey(); if (isset($this->dictionary[$type][$ownerKey])) { foreach ($this->dictionary[$type][$ownerKey] as $model) { $model->setRelation($this->relationName, $result); } } } } /** * Associate the model instance to the given parent. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Database\Eloquent\Model */ public function associate($model) { if ($model instanceof Model) { $foreignKey = $this->ownerKey && $model->{$this->ownerKey} ? $this->ownerKey : $model->getKeyName(); } $this->parent->setAttribute( $this->foreignKey, $model instanceof Model ? $model->{$foreignKey} : null ); $this->parent->setAttribute( $this->morphType, $model instanceof Model ? $model->getMorphClass() : null ); return $this->parent->setRelation($this->relationName, $model); } /** * Dissociate previously associated model from the given parent. * * @return \Illuminate\Database\Eloquent\Model */ public function dissociate() { $this->parent->setAttribute($this->foreignKey, null); $this->parent->setAttribute($this->morphType, null); return $this->parent->setRelation($this->relationName, null); } /** * Touch all of the related models for the relationship. * * @return void */ public function touch() { if (! is_null($this->child->{$this->foreignKey})) { parent::touch(); } } /** * Make a new related instance for the given model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return \Illuminate\Database\Eloquent\Model */ protected function newRelatedInstanceFor(Model $parent) { return $parent->{$this->getRelationName()}()->getRelated()->newInstance(); } /** * Get the foreign key "type" name. * * @return string */ public function getMorphType() { return $this->morphType; } /** * Get the dictionary used by the relationship. * * @return array */ public function getDictionary() { return $this->dictionary; } /** * Specify which relations to load for a given morph type. * * @param array $with * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function morphWith(array $with) { $this->morphableEagerLoads = array_merge( $this->morphableEagerLoads, $with ); return $this; } /** * Specify which relationship counts to load for a given morph type. * * @param array $withCount * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function morphWithCount(array $withCount) { $this->morphableEagerLoadCounts = array_merge( $this->morphableEagerLoadCounts, $withCount ); return $this; } /** * Specify constraints on the query for a given morph type. * * @param array $callbacks * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function constrain(array $callbacks) { $this->morphableConstraints = array_merge( $this->morphableConstraints, $callbacks ); return $this; } /** * Indicate that soft deleted models should be included in the results. * * @return $this */ public function withTrashed() { $callback = fn ($query) => $query->hasMacro('withTrashed') ? $query->withTrashed() : $query; $this->macroBuffer[] = [ 'method' => 'when', 'parameters' => [true, $callback], ]; return $this->when(true, $callback); } /** * Indicate that soft deleted models should not be included in the results. * * @return $this */ public function withoutTrashed() { $callback = fn ($query) => $query->hasMacro('withoutTrashed') ? $query->withoutTrashed() : $query; $this->macroBuffer[] = [ 'method' => 'when', 'parameters' => [true, $callback], ]; return $this->when(true, $callback); } /** * Indicate that only soft deleted models should be included in the results. * * @return $this */ public function onlyTrashed() { $callback = fn ($query) => $query->hasMacro('onlyTrashed') ? $query->onlyTrashed() : $query; $this->macroBuffer[] = [ 'method' => 'when', 'parameters' => [true, $callback], ]; return $this->when(true, $callback); } /** * 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) { $this->macroBuffer[] = compact('method', 'parameters'); return $this; } } } Eloquent/Relations/MorphToMany.php000064400000013375147536122140013244 0ustar00inverse = $inverse; $this->morphType = $name.'_type'; $this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass(); parent::__construct( $query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName ); } /** * Set the where clause for the relation query. * * @return $this */ protected function addWhereConstraints() { parent::addWhereConstraints(); $this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass); return $this; } /** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { parent::addEagerConstraints($models); $this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass); } /** * Create a new pivot attachment record. * * @param int $id * @param bool $timed * @return array */ protected function baseAttachRecord($id, $timed) { return Arr::add( parent::baseAttachRecord($id, $timed), $this->morphType, $this->morphClass ); } /** * Add the constraints for a relationship count query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where( $this->qualifyPivotColumn($this->morphType), $this->morphClass ); } /** * Get the pivot models that are currently attached. * * @return \Illuminate\Support\Collection */ protected function getCurrentlyAttachedPivots() { return parent::getCurrentlyAttachedPivots()->map(function ($record) { return $record instanceof MorphPivot ? $record->setMorphType($this->morphType) ->setMorphClass($this->morphClass) : $record; }); } /** * Create a new query builder for the pivot table. * * @return \Illuminate\Database\Query\Builder */ public function newPivotQuery() { return parent::newPivotQuery()->where($this->morphType, $this->morphClass); } /** * Create a new pivot model instance. * * @param array $attributes * @param bool $exists * @return \Illuminate\Database\Eloquent\Relations\Pivot */ public function newPivot(array $attributes = [], $exists = false) { $using = $this->using; $attributes = array_merge([$this->morphType => $this->morphClass], $attributes); $pivot = $using ? $using::fromRawAttributes($this->parent, $attributes, $this->table, $exists) : MorphPivot::fromAttributes($this->parent, $attributes, $this->table, $exists); $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey) ->setMorphType($this->morphType) ->setMorphClass($this->morphClass); return $pivot; } /** * Get the pivot columns for the relation. * * "pivot_" is prefixed at each column for easy removal later. * * @return array */ protected function aliasedPivotColumns() { $defaults = [$this->foreignPivotKey, $this->relatedPivotKey, $this->morphType]; return collect(array_merge($defaults, $this->pivotColumns))->map(function ($column) { return $this->qualifyPivotColumn($column).' as pivot_'.$column; })->unique()->all(); } /** * Get the foreign key "type" name. * * @return string */ public function getMorphType() { return $this->morphType; } /** * Get the class name of the parent model. * * @return string */ public function getMorphClass() { return $this->morphClass; } /** * Get the indicator for a reverse relationship. * * @return bool */ public function getInverse() { return $this->inverse; } } Eloquent/Relations/Pivot.php000075500000000707147536122140012126 0ustar00query = $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->eagerKeysWereEmpty ? $this->query->getModel()->newCollection() : $this->get(); } /** * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function sole($columns = ['*']) { $result = $this->take(2)->get($columns); $count = $result->count(); if ($count === 0) { throw (new ModelNotFoundException)->setModel(get_class($this->related)); } if ($count > 1) { throw new MultipleRecordsFoundException($count); } return $result->first(); } /** * Execute the query as a "select" statement. * * @param array $columns * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { return $this->query->get($columns); } /** * Touch all of the related models for the relationship. * * @return void */ public function touch() { $model = $this->getRelated(); if (! $model::isIgnoringTouch()) { $this->rawUpdate([ $model->getUpdatedAtColumn() => $model->freshTimestampString(), ]); } } /** * Run a raw update against the base query. * * @param array $attributes * @return int */ public function rawUpdate(array $attributes = []) { return $this->query->withoutGlobalScopes()->update($attributes); } /** * Add the constraints for a relationship count query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceCountQuery(Builder $query, Builder $parentQuery) { return $this->getRelationExistenceQuery( $query, $parentQuery, new Expression('count(*)') )->setBindings([], 'select'); } /** * Add the constraints for an internal relationship existence query. * * Essentially, these queries compare on column names like whereColumn. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parentQuery * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { return $query->select($columns)->whereColumn( $this->getQualifiedParentKeyName(), '=', $this->getExistenceCompareKey() ); } /** * Get a relationship join table hash. * * @param bool $incrementJoinCount * @return string */ public function getRelationCountHash($incrementJoinCount = true) { return 'laravel_reserved_'.($incrementJoinCount ? static::$selfJoinCount++ : static::$selfJoinCount); } /** * Get all of the primary keys for an array of models. * * @param array $models * @param string|null $key * @return array */ protected function getKeys(array $models, $key = null) { return collect($models)->map(function ($value) use ($key) { return $key ? $value->getAttribute($key) : $value->getKey(); })->values()->unique(null, true)->sort()->all(); } /** * Get the query builder that will contain the relationship constraints. * * @return \Illuminate\Database\Eloquent\Builder */ protected function getRelationQuery() { return $this->query; } /** * Get the underlying query for the relation. * * @return \Illuminate\Database\Eloquent\Builder */ public function getQuery() { return $this->query; } /** * Get the base query builder driving the Eloquent builder. * * @return \Illuminate\Database\Query\Builder */ public function getBaseQuery() { return $this->query->getQuery(); } /** * Get a base query builder instance. * * @return \Illuminate\Database\Query\Builder */ public function toBase() { return $this->query->toBase(); } /** * 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(); } /** * Add a whereIn eager constraint for the given set of model keys to be loaded. * * @param string $whereIn * @param string $key * @param array $modelKeys * @param \Illuminate\Database\Eloquent\Builder $query * @return void */ protected function whereInEager(string $whereIn, string $key, array $modelKeys, $query = null) { ($query ?? $this->query)->{$whereIn}($key, $modelKeys); if ($modelKeys === []) { $this->eagerKeysWereEmpty = true; } } /** * Get the name of the "where in" method for eager loading. * * @param \Illuminate\Database\Eloquent\Model $model * @param string $key * @return string */ protected function whereInMethod(Model $model, $key) { return $model->getKeyName() === last(explode('.', $key)) && in_array($model->getKeyType(), ['int', 'integer']) ? 'whereIntegerInRaw' : 'whereIn'; } /** * Prevent polymorphic relationships from being used without model mappings. * * @param bool $requireMorphMap * @return void */ public static function requireMorphMap($requireMorphMap = true) { static::$requireMorphMap = $requireMorphMap; } /** * Determine if polymorphic relationships require explicit model mapping. * * @return bool */ public static function requiresMorphMap() { return static::$requireMorphMap; } /** * Define the morph map for polymorphic relations and require all morphed models to be explicitly mapped. * * @param array $map * @param bool $merge * @return array */ public static function enforceMorphMap(array $map, $merge = true) { static::requireMorphMap(); return static::morphMap($map, $merge); } /** * Set or get the morph map for polymorphic relations. * * @param array|null $map * @param bool $merge * @return array */ public static function morphMap(array $map = null, $merge = true) { $map = static::buildMorphMapFromModels($map); if (is_array($map)) { static::$morphMap = $merge && static::$morphMap ? $map + static::$morphMap : $map; } return static::$morphMap; } /** * Builds a table-keyed array from model class names. * * @param string[]|null $models * @return array|null */ protected static function buildMorphMapFromModels(array $models = null) { if (is_null($models) || ! array_is_list($models)) { return $models; } return array_combine(array_map(function ($model) { return (new $model)->getTable(); }, $models), $models); } /** * Get the model associated with a custom polymorphic type. * * @param string $alias * @return string|null */ public static function getMorphedModel($alias) { return static::$morphMap[$alias] ?? null; } /** * Handle dynamic method calls to the relationship. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } return $this->forwardDecoratedCallTo($this->query, $method, $parameters); } /** * Force a clone of the underlying query builder when cloning. * * @return void */ public function __clone() { $this->query = clone $this->query; } } Eloquent/Scope.php000064400000000536147536122140010133 0ustar00casts[$this->getDeletedAtColumn()])) { $this->casts[$this->getDeletedAtColumn()] = 'datetime'; } } /** * Force a hard delete on a soft deleted model. * * @return bool|null */ public function forceDelete() { if ($this->fireModelEvent('forceDeleting') === false) { return false; } $this->forceDeleting = true; return tap($this->delete(), function ($deleted) { $this->forceDeleting = false; if ($deleted) { $this->fireModelEvent('forceDeleted', false); } }); } /** * Force a hard delete on a soft deleted model without raising any events. * * @return bool|null */ public function forceDeleteQuietly() { return static::withoutEvents(fn () => $this->forceDelete()); } /** * Perform the actual delete query on this model instance. * * @return mixed */ protected function performDeleteOnModel() { if ($this->forceDeleting) { return tap($this->setKeysForSaveQuery($this->newModelQuery())->forceDelete(), function () { $this->exists = false; }); } 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->usesTimestamps() && ! is_null($this->getUpdatedAtColumn())) { $this->{$this->getUpdatedAtColumn()} = $time; $columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time); } $query->update($columns); $this->syncOriginalAttributes(array_keys($columns)); $this->fireModelEvent('trashed', false); } /** * Restore a soft-deleted model instance. * * @return bool */ 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; } /** * Restore a soft-deleted model instance without raising any events. * * @return bool */ public function restoreQuietly() { return static::withoutEvents(fn () => $this->restore()); } /** * Determine if the model instance has been soft-deleted. * * @return bool */ public function trashed() { return ! is_null($this->{$this->getDeletedAtColumn()}); } /** * Register a "softDeleted" model event callback with the dispatcher. * * @param \Closure|string $callback * @return void */ public static function softDeleted($callback) { static::registerModelEvent('trashed', $callback); } /** * Register a "restoring" model event callback with the dispatcher. * * @param \Closure|string $callback * @return void */ public static function restoring($callback) { static::registerModelEvent('restoring', $callback); } /** * Register a "restored" model event callback with the dispatcher. * * @param \Closure|string $callback * @return void */ public static function restored($callback) { static::registerModelEvent('restored', $callback); } /** * Register a "forceDeleting" model event callback with the dispatcher. * * @param \Closure|string $callback * @return void */ public static function forceDeleting($callback) { static::registerModelEvent('forceDeleting', $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::class.'::DELETED_AT') ? static::DELETED_AT : 'deleted_at'; } /** * Get the fully qualified "deleted at" column. * * @return string */ public function getQualifiedDeletedAtColumn() { return $this->qualifyColumn($this->getDeletedAtColumn()); } } Eloquent/SoftDeletingScope.php000064400000011237147536122140012443 0ustar00whereNull($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 restore-or-create extension to the builder. * * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ protected function addRestoreOrCreate(Builder $builder) { $builder->macro('restoreOrCreate', function (Builder $builder, array $attributes = [], array $values = []) { $builder->withTrashed(); return tap($builder->firstOrCreate($attributes, $values), function ($instance) { $instance->restore(); }); }); } /** * Add the create-or-restore extension to the builder. * * @param \Illuminate\Database\Eloquent\Builder $builder * @return void */ protected function addCreateOrRestore(Builder $builder) { $builder->macro('createOrRestore', function (Builder $builder, array $attributes = [], array $values = []) { $builder->withTrashed(); return tap($builder->createOrFirst($attributes, $values), function ($instance) { $instance->restore(); }); }); } /** * 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; }); } } Events/ConnectionEstablished.php000064400000000155147536122140012776 0ustar00connection = $connection; $this->connectionName = $connection->getName(); } } Events/DatabaseBusy.php000064400000001103147536122140011070 0ustar00connectionName = $connectionName; $this->connections = $connections; } } Events/DatabaseRefreshed.php000064400000000732147536122140012064 0ustar00method = $method; $this->migration = $migration; } } Events/MigrationStarted.php000064400000000147147536122140012010 0ustar00method = $method; } } Events/MigrationsStarted.php000064400000000151147536122140012166 0ustar00 */ public $models; /** * Create a new event instance. * * @param array $models * @return void */ public function __construct($models) { $this->models = $models; } } Events/ModelPruningStarting.php000064400000000644147536122140012651 0ustar00 */ public $models; /** * Create a new event instance. * * @param array $models * @return void */ public function __construct($models) { $this->models = $models; } } Events/ModelsPruned.php000064400000001031147536122140011122 0ustar00model = $model; $this->count = $count; } } Events/NoPendingMigrations.php000064400000000574147536122140012452 0ustar00method = $method; } } Events/QueryExecuted.php000064400000002162147536122140011323 0ustar00sql = $sql; $this->time = $time; $this->bindings = $bindings; $this->connection = $connection; $this->connectionName = $connection->getName(); } } Events/SchemaDumped.php000064400000001375147536122140011073 0ustar00connection = $connection; $this->connectionName = $connection->getName(); $this->path = $path; } } Events/SchemaLoaded.php000064400000001375147536122140011045 0ustar00connection = $connection; $this->connectionName = $connection->getName(); $this->path = $path; } } Events/StatementPrepared.php000064400000001173147536122140012157 0ustar00statement = $statement; $this->connection = $connection; } } Events/TransactionBeginning.php000064400000000154147536122140012634 0ustar00isExpression($table)) { return $this->wrap($this->tablePrefix.$table, true); } return $this->getValue($table); } /** * Wrap a value in keyword identifiers. * * @param \Illuminate\Contracts\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); } // If the given value is a JSON selector we will wrap it differently than a // traditional value. We will need to split this path and wrap each part // wrapped, etc. Otherwise, we will simply wrap the value as a string. if ($this->isJsonSelector($value)) { return $this->wrapJsonSelector($value); } 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; } /** * Wrap the given JSON selector. * * @param string $value * @return string * * @throws \RuntimeException */ protected function wrapJsonSelector($value) { throw new RuntimeException('This database engine does not support JSON operations.'); } /** * Determine if the given string is a JSON selector. * * @param string $value * @return bool */ protected function isJsonSelector($value) { return str_contains($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'"; } /** * Escapes a value for safe SQL embedding. * * @param string|float|int|bool|null $value * @param bool $binary * @return string */ public function escape($value, $binary = false) { if (is_null($this->connection)) { throw new RuntimeException("The database driver's grammar implementation does not support escaping values."); } return $this->connection->escape($value, $binary); } /** * Determine if the given value is a raw expression. * * @param mixed $value * @return bool */ public function isExpression($value) { return $value instanceof Expression; } /** * Transforms expressions to their scalar types. * * @param \Illuminate\Contracts\Database\Query\Expression|string|int|float $expression * @return string|int|float */ public function getValue($expression) { if ($this->isExpression($expression)) { return $this->getValue($expression->getValue($this)); } return $expression; } /** * 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; } /** * Set the grammar's database connection. * * @param \Illuminate\Database\Connection $connection * @return $this */ public function setConnection($connection) { $this->connection = $connection; return $this; } } LICENSE.md000064400000002063147536122140006156 0ustar00The 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. LazyLoadingViolationException.php000064400000001403147536122140013241 0ustar00model = $class; $this->relation = $relation; } } LostConnectionException.php000064400000000174147536122140012104 0ustar00 MigrateCommand::class, 'MigrateFresh' => FreshCommand::class, 'MigrateInstall' => InstallCommand::class, 'MigrateRefresh' => RefreshCommand::class, 'MigrateReset' => ResetCommand::class, 'MigrateRollback' => RollbackCommand::class, 'MigrateStatus' => StatusCommand::class, 'MigrateMake' => MigrateMakeCommand::class, ]; /** * 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(MigrateCommand::class, function ($app) { return new MigrateCommand($app['migrator'], $app[Dispatcher::class]); }); } /** * Register the command. * * @return void */ protected function registerMigrateFreshCommand() { $this->app->singleton(FreshCommand::class); } /** * Register the command. * * @return void */ protected function registerMigrateInstallCommand() { $this->app->singleton(InstallCommand::class, function ($app) { return new InstallCommand($app['migration.repository']); }); } /** * Register the command. * * @return void */ protected function registerMigrateMakeCommand() { $this->app->singleton(MigrateMakeCommand::class, 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(RefreshCommand::class); } /** * Register the command. * * @return void */ protected function registerMigrateResetCommand() { $this->app->singleton(ResetCommand::class, function ($app) { return new ResetCommand($app['migrator']); }); } /** * Register the command. * * @return void */ protected function registerMigrateRollbackCommand() { $this->app->singleton(RollbackCommand::class, function ($app) { return new RollbackCommand($app['migrator']); }); } /** * Register the command. * * @return void */ protected function registerMigrateStatusCommand() { $this->app->singleton(StatusCommand::class, 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)); } } Migrations/DatabaseMigrationRepository.php000075500000012570147536122140015064 0ustar00table = $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 the 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 list of the migrations by batch number. * * @param int $batchNumber * @return array */ public function getMigrationsByBatch($batch) { return $this->table() ->where('batch', $batch) ->orderBy('migration', 'desc') ->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); } /** * Delete the migration repository data store. * * @return void */ public function deleteRepository() { $schema = $this->getConnection()->getSchemaBuilder(); $schema->drop($this->table); } /** * Get a query builder for the migration table. * * @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; } } Migrations/Migration.php000075500000001025147536122140011330 0ustar00connection; } } Migrations/MigrationCreator.php000075500000013675147536122140012666 0ustar00files = $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); $path = $this->getPath($name, $path); $this->files->ensureDirectoryExists(dirname($path)); $this->files->put( $path, $this->populateStub($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, $path); 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 $stub * @param string|null $table * @return string */ protected function populateStub($stub, $table) { // 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 * @param string $path * @return void */ protected function firePostCreateHooks($table, $path) { foreach ($this->postCreate as $callback) { $callback($table, $path); } } /** * 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; } } Migrations/MigrationRepositoryInterface.php000075500000003433147536122140015256 0ustar00 */ protected static $requiredPathCache = []; /** * 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->write(Info::class, 'Nothing to migrate'); 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('up')); $this->write(Info::class, 'Running migrations.'); // 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('up')); if ($this->output) { $this->output->writeln(''); } } /** * 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->resolvePath($file); $name = $this->getMigrationName($file); if ($pretend) { return $this->pretendToRun($migration, 'up'); } $this->write(Task::class, $name, fn () => $this->runMigration($migration, 'up')); // 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); } /** * 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->write(Info::class, 'Nothing to rollback.'); return []; } return tap($this->rollbackMigrations($migrations, $paths, $options), function () { if ($this->output) { $this->output->writeln(''); } }); } /** * 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); } if (($batch = $options['batch'] ?? 0) > 0) { return $this->repository->getMigrationsByBatch($batch); } 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('down')); $this->write(Info::class, 'Rolling back migrations.'); // 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->write(TwoColumnDetail::class, $migration->migration, 'Migration not found'); continue; } $rolledBack[] = $file; $this->runDown( $file, $migration, $options['pretend'] ?? false ); } $this->fireMigrationEvent(new MigrationsEnded('down')); 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->write(Info::class, 'Nothing to rollback.'); return []; } return tap($this->resetMigrations($migrations, Arr::wrap($paths), $pretend), function () { if ($this->output) { $this->output->writeln(''); } }); } /** * 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->resolvePath($file); $name = $this->getMigrationName($file); if ($pretend) { return $this->pretendToRun($instance, 'down'); } $this->write(Task::class, $name, fn () => $this->runMigration($instance, 'down')); // 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); } /** * 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 ($connection, $migration, $method) { if (method_exists($migration, $method)) { $this->fireMigrationEvent(new MigrationStarted($migration, $method)); $this->runMethod($connection, $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) { try { $name = get_class($migration); $reflectionClass = new ReflectionClass($migration); if ($reflectionClass->isAnonymous()) { $name = $this->getMigrationName($reflectionClass->getFileName()); } $this->write(TwoColumnDetail::class, $name); $this->write(BulletList::class, collect($this->getQueries($migration, $method))->map(function ($query) { return $query['query']; })); } catch (SchemaException) { $name = get_class($migration); $this->write(Error::class, sprintf( '[%s] failed to dump queries. This may be due to changing database columns using Doctrine, which is not supported while pretending to run migrations.', $name, )); } } /** * 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 ($db, $migration, $method) { if (method_exists($migration, $method)) { $this->runMethod($db, $migration, $method); } }); } /** * Run a migration method on the given connection. * * @param \Illuminate\Database\Connection $connection * @param object $migration * @param string $method * @return void */ protected function runMethod($connection, $migration, $method) { $previousConnection = $this->resolver->getDefaultConnection(); try { $this->resolver->setDefaultConnection($connection->getName()); $migration->{$method}(); } finally { $this->resolver->setDefaultConnection($previousConnection); } } /** * Resolve a migration instance from a file. * * @param string $file * @return object */ public function resolve($file) { $class = $this->getMigrationClass($file); return new $class; } /** * Resolve a migration instance from a migration path. * * @param string $path * @return object */ protected function resolvePath(string $path) { $class = $this->getMigrationClass($this->getMigrationName($path)); if (class_exists($class) && realpath($path) == (new ReflectionClass($class))->getFileName()) { return new $class; } $migration = static::$requiredPathCache[$path] ??= $this->files->getRequire($path); if (is_object($migration)) { return method_exists($migration, '__construct') ? $this->files->getRequire($path) : clone $migration; } return new $class; } /** * Generate a migration class name based on the migration file name. * * @param string $migrationName * @return string */ protected function getMigrationClass(string $migrationName): string { return Str::studly(implode('_', array_slice(explode('_', $migrationName), 4))); } /** * Get all of the migration files in a given path. * * @param string|array $paths * @return array */ public function getMigrationFiles($paths) { return Collection::make($paths)->flatMap(function ($path) { return str_ends_with($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(); } /** * Determine if any migrations have been run. * * @return bool */ public function hasRunAnyMigrations() { return $this->repositoryExists() && count($this->repository->getRan()) > 0; } /** * Delete the migration repository data store. * * @return void */ public function deleteRepository() { $this->repository->deleteRepository(); } /** * 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 to the console's output. * * @param string $component * @param array|string ...$arguments * @return void */ protected function write($component, ...$arguments) { if ($this->output && class_exists($component)) { (new $component($this->output))->render(...$arguments); } else { foreach ($arguments as $argument) { if (is_callable($argument)) { $argument(); } } } } /** * 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); } } } Migrations/stubs/migration.create.stub000075500000001033147536122140014157 0ustar00id(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('{{ table }}'); } }; Migrations/stubs/migration.stub000075500000000570147536122140012722 0ustar00count = $count; parent::__construct("$count records were found.", $code, $previous); } /** * Get the number of records found. * * @return int */ public function getCount() { return $this->count; } } MySqlConnection.php000075500000006074147536122140010361 0ustar00getMessage())); } /** * Determine if the connected database is a MariaDB database. * * @return bool */ public function isMaria() { return str_contains($this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), 'MariaDB'); } /** * Get the default query grammar instance. * * @return \Illuminate\Database\Query\Grammars\MySqlGrammar */ protected function getDefaultQueryGrammar() { ($grammar = new QueryGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * 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() { ($grammar = new SchemaGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * Get the schema state for the connection. * * @param \Illuminate\Filesystem\Filesystem|null $files * @param callable|null $processFactory * @return \Illuminate\Database\Schema\MySqlSchemaState */ public function getSchemaState(Filesystem $files = null, callable $processFactory = null) { return new MySqlSchemaState($this, $files, $processFactory); } /** * Get the default post processor instance. * * @return \Illuminate\Database\Query\Processors\MySqlProcessor */ protected function getDefaultPostProcessor() { return new MySqlProcessor; } /** * Get the Doctrine DBAL driver. * * @return \Illuminate\Database\PDO\MySqlDriver */ protected function getDoctrineDriver() { return new MySqlDriver; } } PDO/Concerns/ConnectsToDatabase.php000064400000001511147536122140013160 0ustar00connection = $connection; } /** * Execute an SQL statement. * * @param string $statement * @return int */ public function exec(string $statement): int { try { $result = $this->connection->exec($statement); \assert($result !== false); return $result; } catch (PDOException $exception) { throw Exception::new($exception); } } /** * Prepare a new SQL statement. * * @param string $sql * @return \Doctrine\DBAL\Driver\Statement * * @throws \Doctrine\DBAL\Driver\PDO\Exception */ public function prepare(string $sql): StatementInterface { try { return $this->createStatement( $this->connection->prepare($sql) ); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * Execute a new query against the connection. * * @param string $sql * @return \Doctrine\DBAL\Driver\Result */ public function query(string $sql): ResultInterface { try { $stmt = $this->connection->query($sql); \assert($stmt instanceof PDOStatement); return new Result($stmt); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * Get the last insert ID. * * @param string|null $name * @return mixed * * @throws \Doctrine\DBAL\Driver\PDO\Exception */ public function lastInsertId($name = null) { try { if ($name === null) { return $this->connection->lastInsertId(); } return $this->connection->lastInsertId($name); } catch (PDOException $exception) { throw Exception::new($exception); } } /** * Create a new statement instance. * * @param \PDOStatement $stmt * @return \Doctrine\DBAL\Driver\PDO\Statement */ protected function createStatement(PDOStatement $stmt): Statement { return new Statement($stmt); } /** * Begin a new database transaction. * * @return bool */ public function beginTransaction() { return $this->connection->beginTransaction(); } /** * Commit a database transaction. * * @return bool */ public function commit() { return $this->connection->commit(); } /** * Rollback a database transaction. * * @return bool */ public function rollBack() { return $this->connection->rollBack(); } /** * Wrap quotes around the given input. * * @param string $input * @param string $type * @return string */ public function quote($input, $type = ParameterType::STRING) { return $this->connection->quote($input, $type); } /** * Get the server version for the connection. * * @return string */ public function getServerVersion() { return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); } /** * Get the wrapped PDO connection. * * @return \PDO */ public function getWrappedConnection(): PDO { return $this->connection; } } PDO/MySqlDriver.php000064400000000515147536122140010126 0ustar00connection = $connection; } /** * Prepare a new SQL statement. * * @param string $sql * @return \Doctrine\DBAL\Driver\Statement */ public function prepare(string $sql): StatementInterface { return new Statement( $this->connection->prepare($sql) ); } /** * Execute a new query against the connection. * * @param string $sql * @return \Doctrine\DBAL\Driver\Result */ public function query(string $sql): Result { return $this->connection->query($sql); } /** * Execute an SQL statement. * * @param string $statement * @return int */ public function exec(string $statement): int { return $this->connection->exec($statement); } /** * Get the last insert ID. * * @param string|null $name * @return mixed */ public function lastInsertId($name = null) { if ($name === null) { return $this->connection->lastInsertId($name); } return $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?') ->execute([$name]) ->fetchOne(); } /** * Begin a new database transaction. * * @return bool */ public function beginTransaction() { return $this->connection->beginTransaction(); } /** * Commit a database transaction. * * @return bool */ public function commit() { return $this->connection->commit(); } /** * Rollback a database transaction. * * @return bool */ public function rollBack() { return $this->connection->rollBack(); } /** * Wrap quotes around the given input. * * @param string $value * @param int $type * @return string */ public function quote($value, $type = ParameterType::STRING) { $val = $this->connection->quote($value, $type); // Fix for a driver version terminating all values with null byte... if (\is_string($val) && str_contains($val, "\0")) { $val = \substr($val, 0, -1); } return $val; } /** * Get the server version for the connection. * * @return string */ public function getServerVersion() { return $this->connection->getServerVersion(); } /** * Get the wrapped PDO connection. * * @return \PDO */ public function getWrappedConnection(): PDO { return $this->connection->getWrappedConnection(); } } PDO/SqlServerDriver.php000064400000001350147536122140011005 0ustar00getCode(); } /** * Get the default query grammar instance. * * @return \Illuminate\Database\Query\Grammars\PostgresGrammar */ protected function getDefaultQueryGrammar() { ($grammar = new QueryGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * 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() { ($grammar = new SchemaGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * Get the schema state for the connection. * * @param \Illuminate\Filesystem\Filesystem|null $files * @param callable|null $processFactory * @return \Illuminate\Database\Schema\PostgresSchemaState */ public function getSchemaState(Filesystem $files = null, callable $processFactory = null) { return new PostgresSchemaState($this, $files, $processFactory); } /** * Get the default post processor instance. * * @return \Illuminate\Database\Query\Processors\PostgresProcessor */ protected function getDefaultPostProcessor() { return new PostgresProcessor; } /** * Get the Doctrine DBAL driver. * * @return \Illuminate\Database\PDO\PostgresDriver */ protected function getDoctrineDriver() { return new PostgresDriver; } } Query/Builder.php000075500000336371147536122140007775 0ustar00 [], 'from' => [], 'join' => [], 'where' => [], 'groupBy' => [], 'having' => [], 'order' => [], 'union' => [], 'unionOrder' => [], ]; /** * An aggregate function and column to be run. * * @var array */ public $aggregate; /** * The columns that should be returned. * * @var array */ public $columns; /** * Indicates if the query returns distinct results. * * Occasionally contains the columns that should be distinct. * * @var bool|array */ public $distinct = false; /** * The table which the query is targeting. * * @var string */ public $from; /** * The index hint for the query. * * @var \Illuminate\Database\Query\IndexHint */ public $indexHint; /** * The table joins for the query. * * @var array */ public $joins; /** * The where constraints for the query. * * @var array */ public $wheres = []; /** * The groupings for the query. * * @var array */ public $groups; /** * The having constraints for the query. * * @var array */ public $havings; /** * The orderings for the query. * * @var array */ public $orders; /** * The maximum number of records to return. * * @var int */ public $limit; /** * The number of records to skip. * * @var int */ public $offset; /** * The query union statements. * * @var array */ public $unions; /** * The maximum number of union records to return. * * @var int */ public $unionLimit; /** * The number of union records to skip. * * @var int */ public $unionOffset; /** * The orderings for the union query. * * @var array */ public $unionOrders; /** * Indicates whether row locking is being used. * * @var string|bool */ public $lock; /** * The callbacks that should be invoked before the query is executed. * * @var array */ public $beforeQueryCallbacks = []; /** * All of the available clause operators. * * @var string[] */ public $operators = [ '=', '<', '>', '<=', '>=', '<>', '!=', '<=>', 'like', 'like binary', 'not like', 'ilike', '&', '|', '^', '<<', '>>', '&~', 'is', 'is not', 'rlike', 'not rlike', 'regexp', 'not regexp', '~', '~*', '!~', '!~*', 'similar to', 'not similar to', 'not ilike', '~~*', '!~~*', ]; /** * All of the available bitwise operators. * * @var string[] */ public $bitwiseOperators = [ '&', '|', '^', '<<', '>>', '&~', ]; /** * Whether to use write pdo for the select. * * @var bool */ public $useWritePdo = false; /** * Create a new query builder instance. * * @param \Illuminate\Database\ConnectionInterface $connection * @param \Illuminate\Database\Query\Grammars\Grammar|null $grammar * @param \Illuminate\Database\Query\Processors\Processor|null $processor * @return void */ public function __construct(ConnectionInterface $connection, Grammar $grammar = null, Processor $processor = null) { $this->connection = $connection; $this->grammar = $grammar ?: $connection->getQueryGrammar(); $this->processor = $processor ?: $connection->getPostProcessor(); } /** * Set the columns to be selected. * * @param array|mixed $columns * @return $this */ public function select($columns = ['*']) { $this->columns = []; $this->bindings['select'] = []; $columns = is_array($columns) ? $columns : func_get_args(); foreach ($columns as $as => $column) { if (is_string($as) && $this->isQueryable($column)) { $this->selectSub($column, $as); } else { $this->columns[] = $column; } } return $this; } /** * Add a subselect expression to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @return $this * * @throws \InvalidArgumentException */ public function selectSub($query, $as) { [$query, $bindings] = $this->createSub($query); return $this->selectRaw( '('.$query.') as '.$this->grammar->wrap($as), $bindings ); } /** * Add a new "raw" select expression to the query. * * @param string $expression * @param array $bindings * @return $this */ public function selectRaw($expression, array $bindings = []) { $this->addSelect(new Expression($expression)); if ($bindings) { $this->addBinding($bindings, 'select'); } return $this; } /** * Makes "from" fetch from a subquery. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @return $this * * @throws \InvalidArgumentException */ public function fromSub($query, $as) { [$query, $bindings] = $this->createSub($query); return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings); } /** * Add a raw from clause to the query. * * @param string $expression * @param mixed $bindings * @return $this */ public function fromRaw($expression, $bindings = []) { $this->from = new Expression($expression); $this->addBinding($bindings, 'from'); return $this; } /** * Creates a subquery and parse it. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @return array */ protected function createSub($query) { // If the given query is a Closure, we will execute it while passing in a new // query instance to the Closure. This will give the developer a chance to // format and work with the query before we cast it to a raw SQL string. if ($query instanceof Closure) { $callback = $query; $callback($query = $this->forSubQuery()); } return $this->parseSub($query); } /** * Parse the subquery into SQL and bindings. * * @param mixed $query * @return array * * @throws \InvalidArgumentException */ protected function parseSub($query) { if ($query instanceof self || $query instanceof EloquentBuilder || $query instanceof Relation) { $query = $this->prependDatabaseNameIfCrossDatabaseQuery($query); return [$query->toSql(), $query->getBindings()]; } elseif (is_string($query)) { return [$query, []]; } else { throw new InvalidArgumentException( 'A subquery must be a query builder instance, a Closure, or a string.' ); } } /** * Prepend the database name if the given query is on another database. * * @param mixed $query * @return mixed */ protected function prependDatabaseNameIfCrossDatabaseQuery($query) { if ($query->getConnection()->getDatabaseName() !== $this->getConnection()->getDatabaseName()) { $databaseName = $query->getConnection()->getDatabaseName(); if (! str_starts_with($query->from, $databaseName) && ! str_contains($query->from, '.')) { $query->from($databaseName.'.'.$query->from); } } return $query; } /** * Add a new select column to the query. * * @param array|mixed $column * @return $this */ public function addSelect($column) { $columns = is_array($column) ? $column : func_get_args(); foreach ($columns as $as => $column) { if (is_string($as) && $this->isQueryable($column)) { if (is_null($this->columns)) { $this->select($this->from.'.*'); } $this->selectSub($column, $as); } else { if (is_array($this->columns) && in_array($column, $this->columns, true)) { continue; } $this->columns[] = $column; } } return $this; } /** * Force the query to only return distinct results. * * @return $this */ public function distinct() { $columns = func_get_args(); if (count($columns) > 0) { $this->distinct = is_array($columns[0]) || is_bool($columns[0]) ? $columns[0] : $columns; } else { $this->distinct = true; } return $this; } /** * Set the table which the query is targeting. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $table * @param string|null $as * @return $this */ public function from($table, $as = null) { if ($this->isQueryable($table)) { return $this->fromSub($table, $as); } $this->from = $as ? "{$table} as {$as}" : $table; return $this; } /** * Add an index hint to suggest a query index. * * @param string $index * @return $this */ public function useIndex($index) { $this->indexHint = new IndexHint('hint', $index); return $this; } /** * Add an index hint to force a query index. * * @param string $index * @return $this */ public function forceIndex($index) { $this->indexHint = new IndexHint('force', $index); return $this; } /** * Add an index hint to ignore a query index. * * @param string $index * @return $this */ public function ignoreIndex($index) { $this->indexHint = new IndexHint('ignore', $index); return $this; } /** * Add a join clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @param string $type * @param bool $where * @return $this */ public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) { $join = $this->newJoinClause($this, $type, $table); // If the first "column" of the join is really a Closure instance the developer // is trying to build a join with a complex "on" clause containing more than // one condition, so we'll add the join and call a Closure with the query. if ($first instanceof Closure) { $first($join); $this->joins[] = $join; $this->addBinding($join->getBindings(), 'join'); } // If the column is simply a string, we can assume the join simply has a basic // "on" clause with a single condition. So we will just build the join with // this simple join clauses attached to it. There is not a join callback. else { $method = $where ? 'where' : 'on'; $this->joins[] = $join->$method($first, $operator, $second); $this->addBinding($join->getBindings(), 'join'); } return $this; } /** * Add a "join where" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string $first * @param string $operator * @param \Illuminate\Contracts\Database\Query\Expression|string $second * @param string $type * @return $this */ public function joinWhere($table, $first, $operator, $second, $type = 'inner') { return $this->join($table, $first, $operator, $second, $type, true); } /** * Add a subquery join clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @param string $type * @param bool $where * @return $this * * @throws \InvalidArgumentException */ public function joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false) { [$query, $bindings] = $this->createSub($query); $expression = '('.$query.') as '.$this->grammar->wrapTable($as); $this->addBinding($bindings, 'join'); return $this->join(new Expression($expression), $first, $operator, $second, $type, $where); } /** * Add a left join to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this */ public function leftJoin($table, $first, $operator = null, $second = null) { return $this->join($table, $first, $operator, $second, 'left'); } /** * Add a "join where" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string $first * @param string $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this */ public function leftJoinWhere($table, $first, $operator, $second) { return $this->joinWhere($table, $first, $operator, $second, 'left'); } /** * Add a subquery left join to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this */ public function leftJoinSub($query, $as, $first, $operator = null, $second = null) { return $this->joinSub($query, $as, $first, $operator, $second, 'left'); } /** * Add a right join to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this */ public function rightJoin($table, $first, $operator = null, $second = null) { return $this->join($table, $first, $operator, $second, 'right'); } /** * Add a "right join where" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string $first * @param string $operator * @param \Illuminate\Contracts\Database\Query\Expression|string $second * @return $this */ public function rightJoinWhere($table, $first, $operator, $second) { return $this->joinWhere($table, $first, $operator, $second, 'right'); } /** * Add a subquery right join to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this */ public function rightJoinSub($query, $as, $first, $operator = null, $second = null) { return $this->joinSub($query, $as, $first, $operator, $second, 'right'); } /** * Add a "cross join" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @param \Closure|string|null $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this */ public function crossJoin($table, $first = null, $operator = null, $second = null) { if ($first) { return $this->join($table, $first, $operator, $second, 'cross'); } $this->joins[] = $this->newJoinClause($this, 'cross', $table); return $this; } /** * Add a subquery cross join to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @return $this */ public function crossJoinSub($query, $as) { [$query, $bindings] = $this->createSub($query); $expression = '('.$query.') as '.$this->grammar->wrapTable($as); $this->addBinding($bindings, 'join'); $this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression)); return $this; } /** * Get a new join clause. * * @param \Illuminate\Database\Query\Builder $parentQuery * @param string $type * @param string $table * @return \Illuminate\Database\Query\JoinClause */ protected function newJoinClause(self $parentQuery, $type, $table) { return new JoinClause($parentQuery, $type, $table); } /** * Merge an array of where clauses and bindings. * * @param array $wheres * @param array $bindings * @return $this */ public function mergeWheres($wheres, $bindings) { $this->wheres = array_merge($this->wheres, (array) $wheres); $this->bindings['where'] = array_values( array_merge($this->bindings['where'], (array) $bindings) ); return $this; } /** * Add a basic where clause to the query. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return $this */ public function where($column, $operator = null, $value = null, $boolean = 'and') { if ($column instanceof ConditionExpression) { $type = 'Expression'; $this->wheres[] = compact('type', 'column', 'boolean'); return $this; } // If the column is an array, we will assume it is an array of key-value pairs // and can add them each as a where clause. We will maintain the boolean we // received when the method was called and pass it into the nested where. if (is_array($column)) { return $this->addArrayOfWheres($column, $boolean); } // Here we will make some assumptions about the operator. If only 2 values are // passed to the method, we will assume that the operator is an equals sign // and keep going. Otherwise, we'll require the operator to be passed in. [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); // If the column is actually a Closure instance, we will assume the developer // wants to begin a nested where statement which is wrapped in parentheses. // We will add that Closure to the query and return back out immediately. if ($column instanceof Closure && is_null($operator)) { return $this->whereNested($column, $boolean); } // If the column is a Closure instance and there is an operator value, we will // assume the developer wants to run a subquery and then compare the result // of that subquery with the given value that was provided to the method. if ($this->isQueryable($column) && ! is_null($operator)) { [$sub, $bindings] = $this->createSub($column); return $this->addBinding($bindings, 'where') ->where(new Expression('('.$sub.')'), $operator, $value, $boolean); } // If the given operator is not found in the list of valid operators we will // assume that the developer is just short-cutting the '=' operators and // we will set the operators to '=' and set the values appropriately. if ($this->invalidOperator($operator)) { [$value, $operator] = [$operator, '=']; } // If the value is a Closure, it means the developer is performing an entire // sub-select within the query and we will need to compile the sub-select // within the where clause to get the appropriate query record results. if ($this->isQueryable($value)) { return $this->whereSub($column, $operator, $value, $boolean); } // If the value is "null", we will just assume the developer wants to add a // where null clause to the query. So, we will allow a short-cut here to // that method for convenience so the developer doesn't have to check. if (is_null($value)) { return $this->whereNull($column, $boolean, $operator !== '='); } $type = 'Basic'; $columnString = ($column instanceof ExpressionContract) ? $this->grammar->getValue($column) : $column; // If the column is making a JSON reference we'll check to see if the value // is a boolean. If it is, we'll add the raw boolean string as an actual // value to the query to ensure this is properly handled by the query. if (str_contains($columnString, '->') && is_bool($value)) { $value = new Expression($value ? 'true' : 'false'); if (is_string($column)) { $type = 'JsonBoolean'; } } if ($this->isBitwiseOperator($operator)) { $type = 'Bitwise'; } // Now that we are working with just a simple query we can put the elements // in our array and add the query binding to our array of bindings that // will be bound to each SQL statements when it is finally executed. $this->wheres[] = compact( 'type', 'column', 'operator', 'value', 'boolean' ); if (! $value instanceof ExpressionContract) { $this->addBinding($this->flattenValue($value), 'where'); } return $this; } /** * Add an array of where clauses to the query. * * @param array $column * @param string $boolean * @param string $method * @return $this */ protected function addArrayOfWheres($column, $boolean, $method = 'where') { return $this->whereNested(function ($query) use ($column, $method, $boolean) { foreach ($column as $key => $value) { if (is_numeric($key) && is_array($value)) { $query->{$method}(...array_values($value)); } else { $query->{$method}($key, '=', $value, $boolean); } } }, $boolean); } /** * Prepare the value and operator for a where clause. * * @param string $value * @param string $operator * @param bool $useDefault * @return array * * @throws \InvalidArgumentException */ public function prepareValueAndOperator($value, $operator, $useDefault = false) { if ($useDefault) { return [$operator, '=']; } elseif ($this->invalidOperatorAndValue($operator, $value)) { throw new InvalidArgumentException('Illegal operator and value combination.'); } return [$value, $operator]; } /** * Determine if the given operator and value combination is legal. * * Prevents using Null values with invalid operators. * * @param string $operator * @param mixed $value * @return bool */ protected function invalidOperatorAndValue($operator, $value) { return is_null($value) && in_array($operator, $this->operators) && ! in_array($operator, ['=', '<>', '!=']); } /** * Determine if the given operator is supported. * * @param string $operator * @return bool */ protected function invalidOperator($operator) { return ! is_string($operator) || (! in_array(strtolower($operator), $this->operators, true) && ! in_array(strtolower($operator), $this->grammar->getOperators(), true)); } /** * Determine if the operator is a bitwise operator. * * @param string $operator * @return bool */ protected function isBitwiseOperator($operator) { return in_array(strtolower($operator), $this->bitwiseOperators, true) || in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true); } /** * Add an "or where" clause to the query. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this */ public function orWhere($column, $operator = null, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->where($column, $operator, $value, 'or'); } /** * Add a basic "where not" clause to the query. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return $this */ public function whereNot($column, $operator = null, $value = null, $boolean = 'and') { if (is_array($column)) { return $this->whereNested(function ($query) use ($column, $operator, $value, $boolean) { $query->where($column, $operator, $value, $boolean); }, $boolean.' not'); } return $this->where($column, $operator, $value, $boolean.' not'); } /** * Add an "or where not" clause to the query. * * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column * @param mixed $operator * @param mixed $value * @return $this */ public function orWhereNot($column, $operator = null, $value = null) { return $this->whereNot($column, $operator, $value, 'or'); } /** * Add a "where" clause comparing two columns to the query. * * @param string|array $first * @param string|null $operator * @param string|null $second * @param string|null $boolean * @return $this */ public function whereColumn($first, $operator = null, $second = null, $boolean = 'and') { // If the column is an array, we will assume it is an array of key-value pairs // and can add them each as a where clause. We will maintain the boolean we // received when the method was called and pass it into the nested where. if (is_array($first)) { return $this->addArrayOfWheres($first, $boolean, 'whereColumn'); } // If the given operator is not found in the list of valid operators we will // assume that the developer is just short-cutting the '=' operators and // we will set the operators to '=' and set the values appropriately. if ($this->invalidOperator($operator)) { [$second, $operator] = [$operator, '=']; } // Finally, we will add this where clause into this array of clauses that we // are building for the query. All of them will be compiled via a grammar // once the query is about to be executed and run against the database. $type = 'Column'; $this->wheres[] = compact( 'type', 'first', 'operator', 'second', 'boolean' ); return $this; } /** * Add an "or where" clause comparing two columns to the query. * * @param string|array $first * @param string|null $operator * @param string|null $second * @return $this */ public function orWhereColumn($first, $operator = null, $second = null) { return $this->whereColumn($first, $operator, $second, 'or'); } /** * Add a raw where clause to the query. * * @param string $sql * @param mixed $bindings * @param string $boolean * @return $this */ public function whereRaw($sql, $bindings = [], $boolean = 'and') { $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean]; $this->addBinding((array) $bindings, 'where'); return $this; } /** * Add a raw or where clause to the query. * * @param string $sql * @param mixed $bindings * @return $this */ public function orWhereRaw($sql, $bindings = []) { return $this->whereRaw($sql, $bindings, 'or'); } /** * Add a "where in" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param mixed $values * @param string $boolean * @param bool $not * @return $this */ public function whereIn($column, $values, $boolean = 'and', $not = false) { $type = $not ? 'NotIn' : 'In'; // If the value is a query builder instance we will assume the developer wants to // look for any values that exist within this given query. So, we will add the // query accordingly so that this query is properly executed when it is run. if ($this->isQueryable($values)) { [$query, $bindings] = $this->createSub($values); $values = [new Expression($query)]; $this->addBinding($bindings, 'where'); } // Next, if the value is Arrayable we need to cast it to its raw array form so we // have the underlying array value instead of an Arrayable object which is not // able to be added as a binding, etc. We will then add to the wheres array. if ($values instanceof Arrayable) { $values = $values->toArray(); } $this->wheres[] = compact('type', 'column', 'values', 'boolean'); if (count($values) !== count(Arr::flatten($values, 1))) { throw new InvalidArgumentException('Nested arrays may not be passed to whereIn method.'); } // Finally, we'll add a binding for each value unless that value is an expression // in which case we will just skip over it since it will be the query as a raw // string and not as a parameterized place-holder to be replaced by the PDO. $this->addBinding($this->cleanBindings($values), 'where'); return $this; } /** * Add an "or where in" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param mixed $values * @return $this */ public function orWhereIn($column, $values) { return $this->whereIn($column, $values, 'or'); } /** * Add a "where not in" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param mixed $values * @param string $boolean * @return $this */ public function whereNotIn($column, $values, $boolean = 'and') { return $this->whereIn($column, $values, $boolean, true); } /** * Add an "or where not in" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param mixed $values * @return $this */ public function orWhereNotIn($column, $values) { return $this->whereNotIn($column, $values, 'or'); } /** * Add a "where in raw" clause for integer values to the query. * * @param string $column * @param \Illuminate\Contracts\Support\Arrayable|array $values * @param string $boolean * @param bool $not * @return $this */ public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = false) { $type = $not ? 'NotInRaw' : 'InRaw'; if ($values instanceof Arrayable) { $values = $values->toArray(); } $values = Arr::flatten($values); foreach ($values as &$value) { $value = (int) $value; } $this->wheres[] = compact('type', 'column', 'values', 'boolean'); return $this; } /** * Add an "or where in raw" clause for integer values to the query. * * @param string $column * @param \Illuminate\Contracts\Support\Arrayable|array $values * @return $this */ public function orWhereIntegerInRaw($column, $values) { return $this->whereIntegerInRaw($column, $values, 'or'); } /** * Add a "where not in raw" clause for integer values to the query. * * @param string $column * @param \Illuminate\Contracts\Support\Arrayable|array $values * @param string $boolean * @return $this */ public function whereIntegerNotInRaw($column, $values, $boolean = 'and') { return $this->whereIntegerInRaw($column, $values, $boolean, true); } /** * Add an "or where not in raw" clause for integer values to the query. * * @param string $column * @param \Illuminate\Contracts\Support\Arrayable|array $values * @return $this */ public function orWhereIntegerNotInRaw($column, $values) { return $this->whereIntegerNotInRaw($column, $values, 'or'); } /** * Add a "where null" clause to the query. * * @param string|array|\Illuminate\Contracts\Database\Query\Expression $columns * @param string $boolean * @param bool $not * @return $this */ public function whereNull($columns, $boolean = 'and', $not = false) { $type = $not ? 'NotNull' : 'Null'; foreach (Arr::wrap($columns) as $column) { $this->wheres[] = compact('type', 'column', 'boolean'); } return $this; } /** * Add an "or where null" clause to the query. * * @param string|array|\Illuminate\Contracts\Database\Query\Expression $column * @return $this */ public function orWhereNull($column) { return $this->whereNull($column, 'or'); } /** * Add a "where not null" clause to the query. * * @param string|array|\Illuminate\Contracts\Database\Query\Expression $columns * @param string $boolean * @return $this */ public function whereNotNull($columns, $boolean = 'and') { return $this->whereNull($columns, $boolean, true); } /** * Add a where between statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param iterable $values * @param string $boolean * @param bool $not * @return $this */ public function whereBetween($column, iterable $values, $boolean = 'and', $not = false) { $type = 'between'; if ($values instanceof CarbonPeriod) { $values = [$values->start, $values->end]; } $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not'); $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'where'); return $this; } /** * Add a where between statement using columns to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param array $values * @param string $boolean * @param bool $not * @return $this */ public function whereBetweenColumns($column, array $values, $boolean = 'and', $not = false) { $type = 'betweenColumns'; $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not'); return $this; } /** * Add an or where between statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param iterable $values * @return $this */ public function orWhereBetween($column, iterable $values) { return $this->whereBetween($column, $values, 'or'); } /** * Add an or where between statement using columns to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param array $values * @return $this */ public function orWhereBetweenColumns($column, array $values) { return $this->whereBetweenColumns($column, $values, 'or'); } /** * Add a where not between statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param iterable $values * @param string $boolean * @return $this */ public function whereNotBetween($column, iterable $values, $boolean = 'and') { return $this->whereBetween($column, $values, $boolean, true); } /** * Add a where not between statement using columns to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param array $values * @param string $boolean * @return $this */ public function whereNotBetweenColumns($column, array $values, $boolean = 'and') { return $this->whereBetweenColumns($column, $values, $boolean, true); } /** * Add an or where not between statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param iterable $values * @return $this */ public function orWhereNotBetween($column, iterable $values) { return $this->whereNotBetween($column, $values, 'or'); } /** * Add an or where not between statement using columns to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param array $values * @return $this */ public function orWhereNotBetweenColumns($column, array $values) { return $this->whereNotBetweenColumns($column, $values, 'or'); } /** * Add an "or where not null" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function orWhereNotNull($column) { return $this->whereNotNull($column, 'or'); } /** * Add a "where date" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|null $value * @param string $boolean * @return $this */ public function whereDate($column, $operator, $value = null, $boolean = 'and') { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { $value = $value->format('Y-m-d'); } return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean); } /** * Add an "or where date" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|null $value * @return $this */ public function orWhereDate($column, $operator, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->whereDate($column, $operator, $value, 'or'); } /** * Add a "where time" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|null $value * @param string $boolean * @return $this */ public function whereTime($column, $operator, $value = null, $boolean = 'and') { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { $value = $value->format('H:i:s'); } return $this->addDateBasedWhere('Time', $column, $operator, $value, $boolean); } /** * Add an "or where time" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|null $value * @return $this */ public function orWhereTime($column, $operator, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->whereTime($column, $operator, $value, 'or'); } /** * Add a "where day" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|int|null $value * @param string $boolean * @return $this */ public function whereDay($column, $operator, $value = null, $boolean = 'and') { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { $value = $value->format('d'); } if (! $value instanceof ExpressionContract) { $value = sprintf('%02d', $value); } return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean); } /** * Add an "or where day" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|int|null $value * @return $this */ public function orWhereDay($column, $operator, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->whereDay($column, $operator, $value, 'or'); } /** * Add a "where month" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|int|null $value * @param string $boolean * @return $this */ public function whereMonth($column, $operator, $value = null, $boolean = 'and') { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { $value = $value->format('m'); } if (! $value instanceof ExpressionContract) { $value = sprintf('%02d', $value); } return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean); } /** * Add an "or where month" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|int|null $value * @return $this */ public function orWhereMonth($column, $operator, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->whereMonth($column, $operator, $value, 'or'); } /** * Add a "where year" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|int|null $value * @param string $boolean * @return $this */ public function whereYear($column, $operator, $value = null, $boolean = 'and') { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { $value = $value->format('Y'); } return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean); } /** * Add an "or where year" statement to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \DateTimeInterface|string|int|null $value * @return $this */ public function orWhereYear($column, $operator, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->whereYear($column, $operator, $value, 'or'); } /** * Add a date based (year, month, day, time) statement to the query. * * @param string $type * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param mixed $value * @param string $boolean * @return $this */ protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and') { $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value'); if (! $value instanceof ExpressionContract) { $this->addBinding($value, 'where'); } return $this; } /** * Add a nested where statement to the query. * * @param \Closure $callback * @param string $boolean * @return $this */ public function whereNested(Closure $callback, $boolean = 'and') { $callback($query = $this->forNestedWhere()); return $this->addNestedWhereQuery($query, $boolean); } /** * Create a new query instance for nested where condition. * * @return \Illuminate\Database\Query\Builder */ public function forNestedWhere() { return $this->newQuery()->from($this->from); } /** * Add another query builder as a nested where to the query builder. * * @param \Illuminate\Database\Query\Builder $query * @param string $boolean * @return $this */ public function addNestedWhereQuery($query, $boolean = 'and') { if (count($query->wheres)) { $type = 'Nested'; $this->wheres[] = compact('type', 'query', 'boolean'); $this->addBinding($query->getRawBindings()['where'], 'where'); } return $this; } /** * Add a full sub-select to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator * @param \Closure||\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback * @param string $boolean * @return $this */ protected function whereSub($column, $operator, $callback, $boolean) { $type = 'Sub'; if ($callback instanceof Closure) { // Once we have the query instance we can simply execute it so it can add all // of the sub-select's conditions to itself, and then we can cache it off // in the array of where clauses for the "main" parent query instance. $callback($query = $this->forSubQuery()); } else { $query = $callback instanceof EloquentBuilder ? $callback->toBase() : $callback; } $this->wheres[] = compact( 'type', 'column', 'operator', 'query', 'boolean' ); $this->addBinding($query->getBindings(), 'where'); return $this; } /** * Add an exists clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback * @param string $boolean * @param bool $not * @return $this */ public function whereExists($callback, $boolean = 'and', $not = false) { if ($callback instanceof Closure) { $query = $this->forSubQuery(); // Similar to the sub-select clause, we will create a new query instance so // the developer may cleanly specify the entire exists query and we will // compile the whole thing in the grammar and insert it into the SQL. $callback($query); } else { $query = $callback instanceof EloquentBuilder ? $callback->toBase() : $callback; } return $this->addWhereExistsQuery($query, $boolean, $not); } /** * Add an or exists clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback * @param bool $not * @return $this */ public function orWhereExists($callback, $not = false) { return $this->whereExists($callback, 'or', $not); } /** * Add a where not exists clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback * @param string $boolean * @return $this */ public function whereNotExists($callback, $boolean = 'and') { return $this->whereExists($callback, $boolean, true); } /** * Add a where not exists clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback * @return $this */ public function orWhereNotExists($callback) { return $this->orWhereExists($callback, true); } /** * Add an exists clause to the query. * * @param \Illuminate\Database\Query\Builder $query * @param string $boolean * @param bool $not * @return $this */ public function addWhereExistsQuery(self $query, $boolean = 'and', $not = false) { $type = $not ? 'NotExists' : 'Exists'; $this->wheres[] = compact('type', 'query', 'boolean'); $this->addBinding($query->getBindings(), 'where'); return $this; } /** * Adds a where condition using row values. * * @param array $columns * @param string $operator * @param array $values * @param string $boolean * @return $this * * @throws \InvalidArgumentException */ public function whereRowValues($columns, $operator, $values, $boolean = 'and') { if (count($columns) !== count($values)) { throw new InvalidArgumentException('The number of columns must match the number of values'); } $type = 'RowValues'; $this->wheres[] = compact('type', 'columns', 'operator', 'values', 'boolean'); $this->addBinding($this->cleanBindings($values)); return $this; } /** * Adds an or where condition using row values. * * @param array $columns * @param string $operator * @param array $values * @return $this */ public function orWhereRowValues($columns, $operator, $values) { return $this->whereRowValues($columns, $operator, $values, 'or'); } /** * Add a "where JSON contains" clause to the query. * * @param string $column * @param mixed $value * @param string $boolean * @param bool $not * @return $this */ public function whereJsonContains($column, $value, $boolean = 'and', $not = false) { $type = 'JsonContains'; $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not'); if (! $value instanceof ExpressionContract) { $this->addBinding($this->grammar->prepareBindingForJsonContains($value)); } return $this; } /** * Add an "or where JSON contains" clause to the query. * * @param string $column * @param mixed $value * @return $this */ public function orWhereJsonContains($column, $value) { return $this->whereJsonContains($column, $value, 'or'); } /** * Add a "where JSON not contains" clause to the query. * * @param string $column * @param mixed $value * @param string $boolean * @return $this */ public function whereJsonDoesntContain($column, $value, $boolean = 'and') { return $this->whereJsonContains($column, $value, $boolean, true); } /** * Add an "or where JSON not contains" clause to the query. * * @param string $column * @param mixed $value * @return $this */ public function orWhereJsonDoesntContain($column, $value) { return $this->whereJsonDoesntContain($column, $value, 'or'); } /** * Add a clause that determines if a JSON path exists to the query. * * @param string $column * @param string $boolean * @param bool $not * @return $this */ public function whereJsonContainsKey($column, $boolean = 'and', $not = false) { $type = 'JsonContainsKey'; $this->wheres[] = compact('type', 'column', 'boolean', 'not'); return $this; } /** * Add an "or" clause that determines if a JSON path exists to the query. * * @param string $column * @return $this */ public function orWhereJsonContainsKey($column) { return $this->whereJsonContainsKey($column, 'or'); } /** * Add a clause that determines if a JSON path does not exist to the query. * * @param string $column * @param string $boolean * @return $this */ public function whereJsonDoesntContainKey($column, $boolean = 'and') { return $this->whereJsonContainsKey($column, $boolean, true); } /** * Add an "or" clause that determines if a JSON path does not exist to the query. * * @param string $column * @return $this */ public function orWhereJsonDoesntContainKey($column) { return $this->whereJsonDoesntContainKey($column, 'or'); } /** * Add a "where JSON length" clause to the query. * * @param string $column * @param mixed $operator * @param mixed $value * @param string $boolean * @return $this */ public function whereJsonLength($column, $operator, $value = null, $boolean = 'and') { $type = 'JsonLength'; [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean'); if (! $value instanceof ExpressionContract) { $this->addBinding((int) $this->flattenValue($value)); } return $this; } /** * Add an "or where JSON length" clause to the query. * * @param string $column * @param mixed $operator * @param mixed $value * @return $this */ public function orWhereJsonLength($column, $operator, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->whereJsonLength($column, $operator, $value, 'or'); } /** * Handles dynamic "where" clauses to the query. * * @param string $method * @param array $parameters * @return $this */ public function dynamicWhere($method, $parameters) { $finder = substr($method, 5); $segments = preg_split( '/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE ); // The connector variable will determine which connector will be used for the // query condition. We will change it as we come across new boolean values // in the dynamic method strings, which could contain a number of these. $connector = 'and'; $index = 0; foreach ($segments as $segment) { // If the segment is not a boolean connector, we can assume it is a column's name // and we will add it to the query as a new constraint as a where clause, then // we can keep iterating through the dynamic method string's segments again. if ($segment !== 'And' && $segment !== 'Or') { $this->addDynamic($segment, $connector, $parameters, $index); $index++; } // Otherwise, we will store the connector so we know how the next where clause we // find in the query should be connected to the previous ones, meaning we will // have the proper boolean connector to connect the next where clause found. else { $connector = $segment; } } return $this; } /** * Add a single dynamic where clause statement to the query. * * @param string $segment * @param string $connector * @param array $parameters * @param int $index * @return void */ protected function addDynamic($segment, $connector, $parameters, $index) { // Once we have parsed out the columns and formatted the boolean operators we // are ready to add it to this query as a where clause just like any other // clause on the query. Then we'll increment the parameter index values. $bool = strtolower($connector); $this->where(Str::snake($segment), '=', $parameters[$index], $bool); } /** * Add a "where fulltext" clause to the query. * * @param string|string[] $columns * @param string $value * @param string $boolean * @return $this */ public function whereFullText($columns, $value, array $options = [], $boolean = 'and') { $type = 'Fulltext'; $columns = (array) $columns; $this->wheres[] = compact('type', 'columns', 'value', 'options', 'boolean'); $this->addBinding($value); return $this; } /** * Add a "or where fulltext" clause to the query. * * @param string|string[] $columns * @param string $value * @return $this */ public function orWhereFullText($columns, $value, array $options = []) { return $this->whereFulltext($columns, $value, $options, 'or'); } /** * Add a "group by" clause to the query. * * @param array|\Illuminate\Contracts\Database\Query\Expression|string ...$groups * @return $this */ public function groupBy(...$groups) { foreach ($groups as $group) { $this->groups = array_merge( (array) $this->groups, Arr::wrap($group) ); } return $this; } /** * Add a raw groupBy clause to the query. * * @param string $sql * @param array $bindings * @return $this */ public function groupByRaw($sql, array $bindings = []) { $this->groups[] = new Expression($sql); $this->addBinding($bindings, 'groupBy'); return $this; } /** * Add a "having" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|\Closure|string $column * @param string|int|float|null $operator * @param string|int|float|null $value * @param string $boolean * @return $this */ public function having($column, $operator = null, $value = null, $boolean = 'and') { $type = 'Basic'; if ($column instanceof ConditionExpression) { $type = 'Expression'; $this->havings[] = compact('type', 'column', 'boolean'); return $this; } // Here we will make some assumptions about the operator. If only 2 values are // passed to the method, we will assume that the operator is an equals sign // and keep going. Otherwise, we'll require the operator to be passed in. [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); if ($column instanceof Closure && is_null($operator)) { return $this->havingNested($column, $boolean); } // If the given operator is not found in the list of valid operators we will // assume that the developer is just short-cutting the '=' operators and // we will set the operators to '=' and set the values appropriately. if ($this->invalidOperator($operator)) { [$value, $operator] = [$operator, '=']; } if ($this->isBitwiseOperator($operator)) { $type = 'Bitwise'; } $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean'); if (! $value instanceof ExpressionContract) { $this->addBinding($this->flattenValue($value), 'having'); } return $this; } /** * Add an "or having" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|\Closure|string $column * @param string|int|float|null $operator * @param string|int|float|null $value * @return $this */ public function orHaving($column, $operator = null, $value = null) { [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); return $this->having($column, $operator, $value, 'or'); } /** * Add a nested having statement to the query. * * @param \Closure $callback * @param string $boolean * @return $this */ public function havingNested(Closure $callback, $boolean = 'and') { $callback($query = $this->forNestedWhere()); return $this->addNestedHavingQuery($query, $boolean); } /** * Add another query builder as a nested having to the query builder. * * @param \Illuminate\Database\Query\Builder $query * @param string $boolean * @return $this */ public function addNestedHavingQuery($query, $boolean = 'and') { if (count($query->havings)) { $type = 'Nested'; $this->havings[] = compact('type', 'query', 'boolean'); $this->addBinding($query->getRawBindings()['having'], 'having'); } return $this; } /** * Add a "having null" clause to the query. * * @param string|array $columns * @param string $boolean * @param bool $not * @return $this */ public function havingNull($columns, $boolean = 'and', $not = false) { $type = $not ? 'NotNull' : 'Null'; foreach (Arr::wrap($columns) as $column) { $this->havings[] = compact('type', 'column', 'boolean'); } return $this; } /** * Add an "or having null" clause to the query. * * @param string $column * @return $this */ public function orHavingNull($column) { return $this->havingNull($column, 'or'); } /** * Add a "having not null" clause to the query. * * @param string|array $columns * @param string $boolean * @return $this */ public function havingNotNull($columns, $boolean = 'and') { return $this->havingNull($columns, $boolean, true); } /** * Add an "or having not null" clause to the query. * * @param string $column * @return $this */ public function orHavingNotNull($column) { return $this->havingNotNull($column, 'or'); } /** * Add a "having between " clause to the query. * * @param string $column * @param iterable $values * @param string $boolean * @param bool $not * @return $this */ public function havingBetween($column, iterable $values, $boolean = 'and', $not = false) { $type = 'between'; if ($values instanceof CarbonPeriod) { $values = [$values->start, $values->end]; } $this->havings[] = compact('type', 'column', 'values', 'boolean', 'not'); $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'having'); return $this; } /** * Add a raw having clause to the query. * * @param string $sql * @param array $bindings * @param string $boolean * @return $this */ public function havingRaw($sql, array $bindings = [], $boolean = 'and') { $type = 'Raw'; $this->havings[] = compact('type', 'sql', 'boolean'); $this->addBinding($bindings, 'having'); return $this; } /** * Add a raw or having clause to the query. * * @param string $sql * @param array $bindings * @return $this */ public function orHavingRaw($sql, array $bindings = []) { return $this->havingRaw($sql, $bindings, 'or'); } /** * Add an "order by" clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Contracts\Database\Query\Expression|string $column * @param string $direction * @return $this * * @throws \InvalidArgumentException */ public function orderBy($column, $direction = 'asc') { if ($this->isQueryable($column)) { [$query, $bindings] = $this->createSub($column); $column = new Expression('('.$query.')'); $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order'); } $direction = strtolower($direction); if (! in_array($direction, ['asc', 'desc'], true)) { throw new InvalidArgumentException('Order direction must be "asc" or "desc".'); } $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [ 'column' => $column, 'direction' => $direction, ]; return $this; } /** * Add a descending "order by" clause to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|\Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function orderByDesc($column) { return $this->orderBy($column, 'desc'); } /** * Add an "order by" clause for a timestamp to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function latest($column = 'created_at') { return $this->orderBy($column, 'desc'); } /** * Add an "order by" clause for a timestamp to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function oldest($column = 'created_at') { return $this->orderBy($column, 'asc'); } /** * Put the query's results in random order. * * @param string|int $seed * @return $this */ public function inRandomOrder($seed = '') { return $this->orderByRaw($this->grammar->compileRandom($seed)); } /** * Add a raw "order by" clause to the query. * * @param string $sql * @param array $bindings * @return $this */ public function orderByRaw($sql, $bindings = []) { $type = 'Raw'; $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'sql'); $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order'); return $this; } /** * Alias to set the "offset" value of the query. * * @param int $value * @return $this */ public function skip($value) { return $this->offset($value); } /** * Set the "offset" value of the query. * * @param int $value * @return $this */ public function offset($value) { $property = $this->unions ? 'unionOffset' : 'offset'; $this->$property = max(0, (int) $value); return $this; } /** * Alias to set the "limit" value of the query. * * @param int $value * @return $this */ public function take($value) { return $this->limit($value); } /** * Set the "limit" value of the query. * * @param int $value * @return $this */ public function limit($value) { $property = $this->unions ? 'unionLimit' : 'limit'; if ($value >= 0) { $this->$property = ! is_null($value) ? (int) $value : null; } return $this; } /** * Set the limit and offset for a given page. * * @param int $page * @param int $perPage * @return $this */ public function forPage($page, $perPage = 15) { return $this->offset(($page - 1) * $perPage)->limit($perPage); } /** * Constrain the query to the previous "page" of results before a given ID. * * @param int $perPage * @param int|null $lastId * @param string $column * @return $this */ public function forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id') { $this->orders = $this->removeExistingOrdersFor($column); if (! is_null($lastId)) { $this->where($column, '<', $lastId); } return $this->orderBy($column, 'desc') ->limit($perPage); } /** * Constrain the query to the next "page" of results after a given ID. * * @param int $perPage * @param int|null $lastId * @param string $column * @return $this */ public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id') { $this->orders = $this->removeExistingOrdersFor($column); if (! is_null($lastId)) { $this->where($column, '>', $lastId); } return $this->orderBy($column, 'asc') ->limit($perPage); } /** * Remove all existing orders and optionally add a new order. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Contracts\Database\Query\Expression|string|null $column * @param string $direction * @return $this */ public function reorder($column = null, $direction = 'asc') { $this->orders = null; $this->unionOrders = null; $this->bindings['order'] = []; $this->bindings['unionOrder'] = []; if ($column) { return $this->orderBy($column, $direction); } return $this; } /** * Get an array with all orders with a given column removed. * * @param string $column * @return array */ protected function removeExistingOrdersFor($column) { return Collection::make($this->orders) ->reject(function ($order) use ($column) { return isset($order['column']) ? $order['column'] === $column : false; })->values()->all(); } /** * Add a union statement to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query * @param bool $all * @return $this */ public function union($query, $all = false) { if ($query instanceof Closure) { $query($query = $this->newQuery()); } $this->unions[] = compact('query', 'all'); $this->addBinding($query->getBindings(), 'union'); return $this; } /** * Add a union all statement to the query. * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query * @return $this */ public function unionAll($query) { return $this->union($query, true); } /** * Lock the selected rows in the table. * * @param string|bool $value * @return $this */ public function lock($value = true) { $this->lock = $value; if (! is_null($this->lock)) { $this->useWritePdo(); } return $this; } /** * Lock the selected rows in the table for updating. * * @return $this */ public function lockForUpdate() { return $this->lock(true); } /** * Share lock the selected rows in the table. * * @return $this */ public function sharedLock() { return $this->lock(false); } /** * Register a closure to be invoked before the query is executed. * * @param callable $callback * @return $this */ public function beforeQuery(callable $callback) { $this->beforeQueryCallbacks[] = $callback; return $this; } /** * Invoke the "before query" modification callbacks. * * @return void */ public function applyBeforeQueryCallbacks() { foreach ($this->beforeQueryCallbacks as $callback) { $callback($this); } $this->beforeQueryCallbacks = []; } /** * Get the SQL representation of the query. * * @return string */ public function toSql() { $this->applyBeforeQueryCallbacks(); return $this->grammar->compileSelect($this); } /** * Get the raw SQL representation of the query with embedded bindings. * * @return string */ public function toRawSql() { return $this->grammar->substituteBindingsIntoRawSql( $this->toSql(), $this->connection->prepareBindings($this->getBindings()) ); } /** * Execute a query for a single record by ID. * * @param int|string $id * @param array|string $columns * @return mixed|static */ public function find($id, $columns = ['*']) { return $this->where('id', '=', $id)->first($columns); } /** * Execute a query for a single record by ID or call a callback. * * @param mixed $id * @param \Closure|array|string $columns * @param \Closure|null $callback * @return mixed|static */ public function findOr($id, $columns = ['*'], Closure $callback = null) { if ($columns instanceof Closure) { $callback = $columns; $columns = ['*']; } if (! is_null($data = $this->find($id, $columns))) { return $data; } return $callback(); } /** * Get a single column's value from the first result of a query. * * @param string $column * @return mixed */ public function value($column) { $result = (array) $this->first([$column]); return count($result) > 0 ? reset($result) : null; } /** * Get a single expression value from the first result of a query. * * @param string $expression * @param array $bindings * @return mixed */ public function rawValue(string $expression, array $bindings = []) { $result = (array) $this->selectRaw($expression, $bindings)->first(); return count($result) > 0 ? reset($result) : null; } /** * Get a single column's value from the first result of a query if it's the sole matching record. * * @param string $column * @return mixed * * @throws \Illuminate\Database\RecordsNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function soleValue($column) { $result = (array) $this->sole([$column]); return reset($result); } /** * Execute the query as a "select" statement. * * @param array|string $columns * @return \Illuminate\Support\Collection */ public function get($columns = ['*']) { return collect($this->onceWithColumns(Arr::wrap($columns), function () { return $this->processor->processSelect($this, $this->runSelect()); })); } /** * Run the query as a "select" statement against the connection. * * @return array */ protected function runSelect() { return $this->connection->select( $this->toSql(), $this->getBindings(), ! $this->useWritePdo ); } /** * Paginate the given query into a simple paginator. * * @param int|\Closure $perPage * @param array|string $columns * @param string $pageName * @param int|null $page * @param \Closure|int|null $total * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) { $page = $page ?: Paginator::resolveCurrentPage($pageName); $total = func_num_args() === 5 ? value(func_get_arg(4)) : $this->getCountForPagination(); $perPage = $perPage instanceof Closure ? $perPage($total) : $perPage; $results = $total ? $this->forPage($page, $perPage)->get($columns) : collect(); return $this->paginator($results, $total, $perPage, $page, [ 'path' => Paginator::resolveCurrentPath(), 'pageName' => $pageName, ]); } /** * Get a paginator only supporting simple next and previous links. * * This is more efficient on larger data-sets, etc. * * @param int $perPage * @param array|string $columns * @param string $pageName * @param int|null $page * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null) { $page = $page ?: Paginator::resolveCurrentPage($pageName); $this->offset(($page - 1) * $perPage)->limit($perPage + 1); return $this->simplePaginator($this->get($columns), $perPage, $page, [ 'path' => Paginator::resolveCurrentPath(), 'pageName' => $pageName, ]); } /** * Get a paginator only supporting simple next and previous links. * * This is more efficient on larger data-sets, etc. * * @param int|null $perPage * @param array|string $columns * @param string $cursorName * @param \Illuminate\Pagination\Cursor|string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator */ public function cursorPaginate($perPage = 15, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor); } /** * Ensure the proper order by required for cursor pagination. * * @param bool $shouldReverse * @return \Illuminate\Support\Collection */ protected function ensureOrderForCursorPagination($shouldReverse = false) { if (empty($this->orders) && empty($this->unionOrders)) { $this->enforceOrderBy(); } $reverseDirection = function ($order) { if (! isset($order['direction'])) { return $order; } $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc'; return $order; }; if ($shouldReverse) { $this->orders = collect($this->orders)->map($reverseDirection)->toArray(); $this->unionOrders = collect($this->unionOrders)->map($reverseDirection)->toArray(); } $orders = ! empty($this->unionOrders) ? $this->unionOrders : $this->orders; return collect($orders) ->filter(fn ($order) => Arr::has($order, 'direction')) ->values(); } /** * Get the count of the total records for the paginator. * * @param array $columns * @return int */ public function getCountForPagination($columns = ['*']) { $results = $this->runPaginationCountQuery($columns); // Once we have run the pagination count query, we will get the resulting count and // take into account what type of query it was. When there is a group by we will // just return the count of the entire results set since that will be correct. if (! isset($results[0])) { return 0; } elseif (is_object($results[0])) { return (int) $results[0]->aggregate; } return (int) array_change_key_case((array) $results[0])['aggregate']; } /** * Run a pagination count query. * * @param array $columns * @return array */ protected function runPaginationCountQuery($columns = ['*']) { if ($this->groups || $this->havings) { $clone = $this->cloneForPaginationCount(); if (is_null($clone->columns) && ! empty($this->joins)) { $clone->select($this->from.'.*'); } return $this->newQuery() ->from(new Expression('('.$clone->toSql().') as '.$this->grammar->wrap('aggregate_table'))) ->mergeBindings($clone) ->setAggregate('count', $this->withoutSelectAliases($columns)) ->get()->all(); } $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; return $this->cloneWithout($without) ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order']) ->setAggregate('count', $this->withoutSelectAliases($columns)) ->get()->all(); } /** * Clone the existing query instance for usage in a pagination subquery. * * @return self */ protected function cloneForPaginationCount() { return $this->cloneWithout(['orders', 'limit', 'offset']) ->cloneWithoutBindings(['order']); } /** * Remove the column aliases since they will break count queries. * * @param array $columns * @return array */ protected function withoutSelectAliases(array $columns) { return array_map(function ($column) { return is_string($column) && ($aliasPosition = stripos($column, ' as ')) !== false ? substr($column, 0, $aliasPosition) : $column; }, $columns); } /** * Get a lazy collection for the given query. * * @return \Illuminate\Support\LazyCollection */ public function cursor() { if (is_null($this->columns)) { $this->columns = ['*']; } return new LazyCollection(function () { yield from $this->connection->cursor( $this->toSql(), $this->getBindings(), ! $this->useWritePdo ); }); } /** * Throw an exception if the query doesn't have an orderBy clause. * * @return void * * @throws \RuntimeException */ protected function enforceOrderBy() { if (empty($this->orders) && empty($this->unionOrders)) { throw new RuntimeException('You must specify an orderBy clause when using this function.'); } } /** * Get a collection instance containing the values of a given column. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string|null $key * @return \Illuminate\Support\Collection */ public function pluck($column, $key = null) { // First, we will need to select the results of the query accounting for the // given columns / key. Once we have the results, we will be able to take // the results and get the exact data that was requested for the query. $queryResult = $this->onceWithColumns( is_null($key) ? [$column] : [$column, $key], function () { return $this->processor->processSelect( $this, $this->runSelect() ); } ); if (empty($queryResult)) { return collect(); } // If the columns are qualified with a table or have an alias, we cannot use // those directly in the "pluck" operations since the results from the DB // are only keyed by the column itself. We'll strip the table out here. $column = $this->stripTableForPluck($column); $key = $this->stripTableForPluck($key); return is_array($queryResult[0]) ? $this->pluckFromArrayColumn($queryResult, $column, $key) : $this->pluckFromObjectColumn($queryResult, $column, $key); } /** * Strip off the table name or alias from a column identifier. * * @param string $column * @return string|null */ protected function stripTableForPluck($column) { if (is_null($column)) { return $column; } $columnString = $column instanceof ExpressionContract ? $this->grammar->getValue($column) : $column; $separator = str_contains(strtolower($columnString), ' as ') ? ' as ' : '\.'; return last(preg_split('~'.$separator.'~i', $columnString)); } /** * Retrieve column values from rows represented as objects. * * @param array $queryResult * @param string $column * @param string $key * @return \Illuminate\Support\Collection */ protected function pluckFromObjectColumn($queryResult, $column, $key) { $results = []; if (is_null($key)) { foreach ($queryResult as $row) { $results[] = $row->$column; } } else { foreach ($queryResult as $row) { $results[$row->$key] = $row->$column; } } return collect($results); } /** * Retrieve column values from rows represented as arrays. * * @param array $queryResult * @param string $column * @param string $key * @return \Illuminate\Support\Collection */ protected function pluckFromArrayColumn($queryResult, $column, $key) { $results = []; if (is_null($key)) { foreach ($queryResult as $row) { $results[] = $row[$column]; } } else { foreach ($queryResult as $row) { $results[$row[$key]] = $row[$column]; } } return collect($results); } /** * Concatenate values of a given column as a string. * * @param string $column * @param string $glue * @return string */ public function implode($column, $glue = '') { return $this->pluck($column)->implode($glue); } /** * Determine if any rows exist for the current query. * * @return bool */ public function exists() { $this->applyBeforeQueryCallbacks(); $results = $this->connection->select( $this->grammar->compileExists($this), $this->getBindings(), ! $this->useWritePdo ); // If the results have rows, we will get the row and see if the exists column is a // boolean true. If there are no results for this query we will return false as // there are no rows for this query at all, and we can return that info here. if (isset($results[0])) { $results = (array) $results[0]; return (bool) $results['exists']; } return false; } /** * Determine if no rows exist for the current query. * * @return bool */ public function doesntExist() { return ! $this->exists(); } /** * Execute the given callback if no rows exist for the current query. * * @param \Closure $callback * @return mixed */ public function existsOr(Closure $callback) { return $this->exists() ? true : $callback(); } /** * Execute the given callback if rows exist for the current query. * * @param \Closure $callback * @return mixed */ public function doesntExistOr(Closure $callback) { return $this->doesntExist() ? true : $callback(); } /** * Retrieve the "count" result of the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $columns * @return int */ public function count($columns = '*') { return (int) $this->aggregate(__FUNCTION__, Arr::wrap($columns)); } /** * Retrieve the minimum value of a given column. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return mixed */ public function min($column) { return $this->aggregate(__FUNCTION__, [$column]); } /** * Retrieve the maximum value of a given column. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return mixed */ public function max($column) { return $this->aggregate(__FUNCTION__, [$column]); } /** * Retrieve the sum of the values of a given column. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return mixed */ public function sum($column) { $result = $this->aggregate(__FUNCTION__, [$column]); return $result ?: 0; } /** * Retrieve the average of the values of a given column. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return mixed */ public function avg($column) { return $this->aggregate(__FUNCTION__, [$column]); } /** * Alias for the "avg" method. * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return mixed */ public function average($column) { return $this->avg($column); } /** * Execute an aggregate function on the database. * * @param string $function * @param array $columns * @return mixed */ public function aggregate($function, $columns = ['*']) { $results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns']) ->cloneWithoutBindings($this->unions || $this->havings ? [] : ['select']) ->setAggregate($function, $columns) ->get($columns); if (! $results->isEmpty()) { return array_change_key_case((array) $results[0])['aggregate']; } } /** * Execute a numeric aggregate function on the database. * * @param string $function * @param array $columns * @return float|int */ public function numericAggregate($function, $columns = ['*']) { $result = $this->aggregate($function, $columns); // If there is no result, we can obviously just return 0 here. Next, we will check // if the result is an integer or float. If it is already one of these two data // types we can just return the result as-is, otherwise we will convert this. if (! $result) { return 0; } if (is_int($result) || is_float($result)) { return $result; } // If the result doesn't contain a decimal place, we will assume it is an int then // cast it to one. When it does we will cast it to a float since it needs to be // cast to the expected data type for the developers out of pure convenience. return ! str_contains((string) $result, '.') ? (int) $result : (float) $result; } /** * Set the aggregate property without running the query. * * @param string $function * @param array $columns * @return $this */ protected function setAggregate($function, $columns) { $this->aggregate = compact('function', 'columns'); if (empty($this->groups)) { $this->orders = null; $this->bindings['order'] = []; } return $this; } /** * Execute the given callback while selecting the given columns. * * After running the callback, the columns are reset to the original value. * * @param array $columns * @param callable $callback * @return mixed */ protected function onceWithColumns($columns, $callback) { $original = $this->columns; if (is_null($original)) { $this->columns = $columns; } $result = $callback(); $this->columns = $original; return $result; } /** * Insert new records into the database. * * @param array $values * @return bool */ public function insert(array $values) { // Since every insert gets treated like a batch insert, we will make sure the // bindings are structured in a way that is convenient when building these // inserts statements by verifying these elements are actually an array. if (empty($values)) { return true; } if (! is_array(reset($values))) { $values = [$values]; } // Here, we will sort the insert keys for every record so that each insert is // in the same order for the record. We need to make sure this is the case // so there are not any errors or problems when inserting these records. else { foreach ($values as $key => $value) { ksort($value); $values[$key] = $value; } } $this->applyBeforeQueryCallbacks(); // Finally, we will run this query against the database connection and return // the results. We will need to also flatten these bindings before running // the query so they are all in one huge, flattened array for execution. return $this->connection->insert( $this->grammar->compileInsert($this, $values), $this->cleanBindings(Arr::flatten($values, 1)) ); } /** * Insert new records into the database while ignoring errors. * * @param array $values * @return int */ public function insertOrIgnore(array $values) { if (empty($values)) { return 0; } if (! is_array(reset($values))) { $values = [$values]; } else { foreach ($values as $key => $value) { ksort($value); $values[$key] = $value; } } $this->applyBeforeQueryCallbacks(); return $this->connection->affectingStatement( $this->grammar->compileInsertOrIgnore($this, $values), $this->cleanBindings(Arr::flatten($values, 1)) ); } /** * Insert a new record and get the value of the primary key. * * @param array $values * @param string|null $sequence * @return int */ public function insertGetId(array $values, $sequence = null) { $this->applyBeforeQueryCallbacks(); $sql = $this->grammar->compileInsertGetId($this, $values, $sequence); $values = $this->cleanBindings($values); return $this->processor->processInsertGetId($this, $sql, $values, $sequence); } /** * Insert new records into the table using a subquery. * * @param array $columns * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @return int */ public function insertUsing(array $columns, $query) { $this->applyBeforeQueryCallbacks(); [$sql, $bindings] = $this->createSub($query); return $this->connection->affectingStatement( $this->grammar->compileInsertUsing($this, $columns, $sql), $this->cleanBindings($bindings) ); } /** * Update records in the database. * * @param array $values * @return int */ public function update(array $values) { $this->applyBeforeQueryCallbacks(); $sql = $this->grammar->compileUpdate($this, $values); return $this->connection->update($sql, $this->cleanBindings( $this->grammar->prepareBindingsForUpdate($this->bindings, $values) )); } /** * Update records in a PostgreSQL database using the update from syntax. * * @param array $values * @return int */ public function updateFrom(array $values) { if (! method_exists($this->grammar, 'compileUpdateFrom')) { throw new LogicException('This database engine does not support the updateFrom method.'); } $this->applyBeforeQueryCallbacks(); $sql = $this->grammar->compileUpdateFrom($this, $values); return $this->connection->update($sql, $this->cleanBindings( $this->grammar->prepareBindingsForUpdateFrom($this->bindings, $values) )); } /** * Insert or update a record matching the attributes, and fill it with values. * * @param array $attributes * @param array $values * @return bool */ public function updateOrInsert(array $attributes, array $values = []) { if (! $this->where($attributes)->exists()) { return $this->insert(array_merge($attributes, $values)); } if (empty($values)) { return true; } return (bool) $this->limit(1)->update($values); } /** * Insert new records or update the existing ones. * * @param array $values * @param array|string $uniqueBy * @param array|null $update * @return int */ public function upsert(array $values, $uniqueBy, $update = null) { if (empty($values)) { return 0; } elseif ($update === []) { return (int) $this->insert($values); } if (! is_array(reset($values))) { $values = [$values]; } else { foreach ($values as $key => $value) { ksort($value); $values[$key] = $value; } } if (is_null($update)) { $update = array_keys(reset($values)); } $this->applyBeforeQueryCallbacks(); $bindings = $this->cleanBindings(array_merge( Arr::flatten($values, 1), collect($update)->reject(function ($value, $key) { return is_int($key); })->all() )); return $this->connection->affectingStatement( $this->grammar->compileUpsert($this, $values, (array) $uniqueBy, $update), $bindings ); } /** * Increment a column's value by a given amount. * * @param string $column * @param float|int $amount * @param array $extra * @return int * * @throws \InvalidArgumentException */ public function increment($column, $amount = 1, array $extra = []) { if (! is_numeric($amount)) { throw new InvalidArgumentException('Non-numeric value passed to increment method.'); } return $this->incrementEach([$column => $amount], $extra); } /** * Increment the given column's values by the given amounts. * * @param array $columns * @param array $extra * @return int * * @throws \InvalidArgumentException */ public function incrementEach(array $columns, array $extra = []) { foreach ($columns as $column => $amount) { if (! is_numeric($amount)) { throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'."); } elseif (! is_string($column)) { throw new InvalidArgumentException('Non-associative array passed to incrementEach method.'); } $columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount"); } return $this->update(array_merge($columns, $extra)); } /** * Decrement a column's value by a given amount. * * @param string $column * @param float|int $amount * @param array $extra * @return int * * @throws \InvalidArgumentException */ public function decrement($column, $amount = 1, array $extra = []) { if (! is_numeric($amount)) { throw new InvalidArgumentException('Non-numeric value passed to decrement method.'); } return $this->decrementEach([$column => $amount], $extra); } /** * Decrement the given column's values by the given amounts. * * @param array $columns * @param array $extra * @return int * * @throws \InvalidArgumentException */ public function decrementEach(array $columns, array $extra = []) { foreach ($columns as $column => $amount) { if (! is_numeric($amount)) { throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'."); } elseif (! is_string($column)) { throw new InvalidArgumentException('Non-associative array passed to decrementEach method.'); } $columns[$column] = $this->raw("{$this->grammar->wrap($column)} - $amount"); } return $this->update(array_merge($columns, $extra)); } /** * Delete records from the database. * * @param mixed $id * @return int */ public function delete($id = null) { // If an ID is passed to the method, we will set the where clause to check the // ID to let developers to simply and quickly remove a single row from this // database without manually specifying the "where" clauses on the query. if (! is_null($id)) { $this->where($this->from.'.id', '=', $id); } $this->applyBeforeQueryCallbacks(); return $this->connection->delete( $this->grammar->compileDelete($this), $this->cleanBindings( $this->grammar->prepareBindingsForDelete($this->bindings) ) ); } /** * Run a truncate statement on the table. * * @return void */ public function truncate() { $this->applyBeforeQueryCallbacks(); foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) { $this->connection->statement($sql, $bindings); } } /** * Get a new instance of the query builder. * * @return \Illuminate\Database\Query\Builder */ public function newQuery() { return new static($this->connection, $this->grammar, $this->processor); } /** * Create a new query instance for a sub-query. * * @return \Illuminate\Database\Query\Builder */ protected function forSubQuery() { return $this->newQuery(); } /** * Get all of the query builder's columns in a text-only array with all expressions evaluated. * * @return array */ public function getColumns() { return ! is_null($this->columns) ? array_map(fn ($column) => $this->grammar->getValue($column), $this->columns) : []; } /** * Create a raw database expression. * * @param mixed $value * @return \Illuminate\Contracts\Database\Query\Expression */ public function raw($value) { return $this->connection->raw($value); } /** * Get the current query value bindings in a flattened array. * * @return array */ public function getBindings() { return Arr::flatten($this->bindings); } /** * Get the raw array of bindings. * * @return array */ public function getRawBindings() { return $this->bindings; } /** * Set the bindings on the query builder. * * @param array $bindings * @param string $type * @return $this * * @throws \InvalidArgumentException */ public function setBindings(array $bindings, $type = 'where') { if (! array_key_exists($type, $this->bindings)) { throw new InvalidArgumentException("Invalid binding type: {$type}."); } $this->bindings[$type] = $bindings; return $this; } /** * Add a binding to the query. * * @param mixed $value * @param string $type * @return $this * * @throws \InvalidArgumentException */ public function addBinding($value, $type = 'where') { if (! array_key_exists($type, $this->bindings)) { throw new InvalidArgumentException("Invalid binding type: {$type}."); } if (is_array($value)) { $this->bindings[$type] = array_values(array_map( [$this, 'castBinding'], array_merge($this->bindings[$type], $value), )); } else { $this->bindings[$type][] = $this->castBinding($value); } return $this; } /** * Cast the given binding value. * * @param mixed $value * @return mixed */ public function castBinding($value) { return $value instanceof BackedEnum ? $value->value : $value; } /** * Merge an array of bindings into our bindings. * * @param \Illuminate\Database\Query\Builder $query * @return $this */ public function mergeBindings(self $query) { $this->bindings = array_merge_recursive($this->bindings, $query->bindings); return $this; } /** * Remove all of the expressions from a list of bindings. * * @param array $bindings * @return array */ public function cleanBindings(array $bindings) { return collect($bindings) ->reject(function ($binding) { return $binding instanceof ExpressionContract; }) ->map([$this, 'castBinding']) ->values() ->all(); } /** * Get a scalar type value from an unknown type of input. * * @param mixed $value * @return mixed */ protected function flattenValue($value) { return is_array($value) ? head(Arr::flatten($value)) : $value; } /** * Get the default key name of the table. * * @return string */ protected function defaultKeyName() { return 'id'; } /** * Get the database connection instance. * * @return \Illuminate\Database\ConnectionInterface */ public function getConnection() { return $this->connection; } /** * Get the database query processor instance. * * @return \Illuminate\Database\Query\Processors\Processor */ public function getProcessor() { return $this->processor; } /** * Get the query grammar instance. * * @return \Illuminate\Database\Query\Grammars\Grammar */ public function getGrammar() { return $this->grammar; } /** * Use the "write" PDO connection when executing the query. * * @return $this */ public function useWritePdo() { $this->useWritePdo = true; return $this; } /** * Determine if the value is a query builder instance or a Closure. * * @param mixed $value * @return bool */ protected function isQueryable($value) { return $value instanceof self || $value instanceof EloquentBuilder || $value instanceof Relation || $value instanceof Closure; } /** * Clone the query. * * @return static */ public function clone() { return clone $this; } /** * Clone the query without the given properties. * * @param array $properties * @return static */ public function cloneWithout(array $properties) { return tap($this->clone(), function ($clone) use ($properties) { foreach ($properties as $property) { $clone->{$property} = null; } }); } /** * Clone the query without the given bindings. * * @param array $except * @return static */ public function cloneWithoutBindings(array $except) { return tap($this->clone(), function ($clone) use ($except) { foreach ($except as $type) { $clone->bindings[$type] = []; } }); } /** * Dump the current SQL and bindings. * * @return $this */ public function dump() { dump($this->toSql(), $this->getBindings()); return $this; } /** * Dump the raw current SQL with embedded bindings. * * @return $this */ public function dumpRawSql() { dump($this->toRawSql()); return $this; } /** * Die and dump the current SQL and bindings. * * @return never */ public function dd() { dd($this->toSql(), $this->getBindings()); } /** * Die and dump the current SQL with embedded bindings. * * @return never */ public function ddRawSql() { dd($this->toRawSql()); } /** * Handle dynamic method calls into the method. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } if (str_starts_with($method, 'where')) { return $this->dynamicWhere($method, $parameters); } static::throwBadMethodCallException($method); } } Query/Expression.php000075500000001371147536122140010533 0ustar00value = $value; } /** * Get the value of the expression. * * @param \Illuminate\Database\Grammar $grammar * @return string|int|float */ public function getValue(Grammar $grammar) { return $this->value; } } Query/Grammars/Grammar.php000075500000114327147536122140011541 0ustar00unions || $query->havings) && $query->aggregate) { return $this->compileUnionAggregate($query); } // If the query does not have any columns set, we'll set the columns to the // * character to just get all of the columns from the database. Then we // can build the query and concatenate all the pieces together as one. $original = $query->columns; if (is_null($query->columns)) { $query->columns = ['*']; } // To compile the query, we'll spin through each component of the query and // see if that component exists. If it does we'll just call the compiler // function for the component which is responsible for making the SQL. $sql = trim($this->concatenate( $this->compileComponents($query)) ); if ($query->unions) { $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query); } $query->columns = $original; return $sql; } /** * Compile the components necessary for a select clause. * * @param \Illuminate\Database\Query\Builder $query * @return array */ protected function compileComponents(Builder $query) { $sql = []; foreach ($this->selectComponents as $component) { if (isset($query->$component)) { $method = 'compile'.ucfirst($component); $sql[$component] = $this->$method($query, $query->$component); } } return $sql; } /** * Compile an aggregated select clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $aggregate * @return string */ protected function compileAggregate(Builder $query, $aggregate) { $column = $this->columnize($aggregate['columns']); // If the query has a "distinct" constraint and we're not asking for all columns // we need to prepend "distinct" onto the column name so that the query takes // it into account when it performs the aggregating operations on the data. if (is_array($query->distinct)) { $column = 'distinct '.$this->columnize($query->distinct); } elseif ($query->distinct && $column !== '*') { $column = 'distinct '.$column; } return 'select '.$aggregate['function'].'('.$column.') as aggregate'; } /** * Compile the "select *" portion of the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $columns * @return string|null */ protected function compileColumns(Builder $query, $columns) { // If the query is actually performing an aggregating select, we will let that // compiler handle the building of the select clauses, as it will need some // more syntax that is best handled by that function to keep things neat. if (! is_null($query->aggregate)) { return; } if ($query->distinct) { $select = 'select distinct '; } else { $select = 'select '; } return $select.$this->columnize($columns); } /** * Compile the "from" portion of the query. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @return string */ protected function compileFrom(Builder $query, $table) { return 'from '.$this->wrapTable($table); } /** * Compile the "join" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $joins * @return string */ protected function compileJoins(Builder $query, $joins) { return collect($joins)->map(function ($join) use ($query) { $table = $this->wrapTable($join->table); $nestedJoins = is_null($join->joins) ? '' : ' '.$this->compileJoins($query, $join->joins); $tableAndNestedJoins = is_null($join->joins) ? $table : '('.$table.$nestedJoins.')'; return trim("{$join->type} join {$tableAndNestedJoins} {$this->compileWheres($join)}"); })->implode(' '); } /** * Compile the "where" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileWheres(Builder $query) { // Each type of where clause has its own compiler function, which is responsible // for actually creating the where clauses SQL. This helps keep the code nice // and maintainable since each clause has a very small method that it uses. if (is_null($query->wheres)) { return ''; } // If we actually have some where clauses, we will strip off the first boolean // operator, which is added by the query builders for convenience so we can // avoid checking for the first clauses in each of the compilers methods. if (count($sql = $this->compileWheresToArray($query)) > 0) { return $this->concatenateWhereClauses($query, $sql); } return ''; } /** * Get an array of all the where clauses for the query. * * @param \Illuminate\Database\Query\Builder $query * @return array */ protected function compileWheresToArray($query) { return collect($query->wheres)->map(function ($where) use ($query) { return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where); })->all(); } /** * Format the where clause statements into one string. * * @param \Illuminate\Database\Query\Builder $query * @param array $sql * @return string */ protected function concatenateWhereClauses($query, $sql) { $conjunction = $query instanceof JoinClause ? 'on' : 'where'; return $conjunction.' '.$this->removeLeadingBoolean(implode(' ', $sql)); } /** * Compile a raw where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereRaw(Builder $query, $where) { return $where['sql'] instanceof Expression ? $where['sql']->getValue($this) : $where['sql']; } /** * Compile a basic where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBasic(Builder $query, $where) { $value = $this->parameter($where['value']); $operator = str_replace('?', '??', $where['operator']); return $this->wrap($where['column']).' '.$operator.' '.$value; } /** * Compile a bitwise operator where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBitwise(Builder $query, $where) { return $this->whereBasic($query, $where); } /** * Compile a "where in" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereIn(Builder $query, $where) { if (! empty($where['values'])) { return $this->wrap($where['column']).' in ('.$this->parameterize($where['values']).')'; } return '0 = 1'; } /** * Compile a "where not in" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNotIn(Builder $query, $where) { if (! empty($where['values'])) { return $this->wrap($where['column']).' not in ('.$this->parameterize($where['values']).')'; } return '1 = 1'; } /** * Compile a "where not in raw" clause. * * For safety, whereIntegerInRaw ensures this method is only used with integer values. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNotInRaw(Builder $query, $where) { if (! empty($where['values'])) { return $this->wrap($where['column']).' not in ('.implode(', ', $where['values']).')'; } return '1 = 1'; } /** * Compile a "where in raw" clause. * * For safety, whereIntegerInRaw ensures this method is only used with integer values. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereInRaw(Builder $query, $where) { if (! empty($where['values'])) { return $this->wrap($where['column']).' in ('.implode(', ', $where['values']).')'; } return '0 = 1'; } /** * Compile a "where null" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNull(Builder $query, $where) { return $this->wrap($where['column']).' is null'; } /** * Compile a "where not null" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNotNull(Builder $query, $where) { return $this->wrap($where['column']).' is not null'; } /** * Compile a "between" where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBetween(Builder $query, $where) { $between = $where['not'] ? 'not between' : 'between'; $min = $this->parameter(is_array($where['values']) ? reset($where['values']) : $where['values'][0]); $max = $this->parameter(is_array($where['values']) ? end($where['values']) : $where['values'][1]); return $this->wrap($where['column']).' '.$between.' '.$min.' and '.$max; } /** * Compile a "between" where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBetweenColumns(Builder $query, $where) { $between = $where['not'] ? 'not between' : 'between'; $min = $this->wrap(is_array($where['values']) ? reset($where['values']) : $where['values'][0]); $max = $this->wrap(is_array($where['values']) ? end($where['values']) : $where['values'][1]); return $this->wrap($where['column']).' '.$between.' '.$min.' and '.$max; } /** * Compile a "where date" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereDate(Builder $query, $where) { return $this->dateBasedWhere('date', $query, $where); } /** * Compile a "where time" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereTime(Builder $query, $where) { return $this->dateBasedWhere('time', $query, $where); } /** * Compile a "where day" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereDay(Builder $query, $where) { return $this->dateBasedWhere('day', $query, $where); } /** * Compile a "where month" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereMonth(Builder $query, $where) { return $this->dateBasedWhere('month', $query, $where); } /** * Compile a "where year" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereYear(Builder $query, $where) { return $this->dateBasedWhere('year', $query, $where); } /** * Compile a date based where clause. * * @param string $type * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function dateBasedWhere($type, Builder $query, $where) { $value = $this->parameter($where['value']); return $type.'('.$this->wrap($where['column']).') '.$where['operator'].' '.$value; } /** * Compile a where clause comparing two columns. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereColumn(Builder $query, $where) { return $this->wrap($where['first']).' '.$where['operator'].' '.$this->wrap($where['second']); } /** * Compile a nested where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNested(Builder $query, $where) { // Here we will calculate what portion of the string we need to remove. If this // is a join clause query, we need to remove the "on" portion of the SQL and // if it is a normal query we need to take the leading "where" of queries. $offset = $where['query'] instanceof JoinClause ? 3 : 6; return '('.substr($this->compileWheres($where['query']), $offset).')'; } /** * Compile a where condition with a sub-select. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereSub(Builder $query, $where) { $select = $this->compileSelect($where['query']); return $this->wrap($where['column']).' '.$where['operator']." ($select)"; } /** * Compile a where exists clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereExists(Builder $query, $where) { return 'exists ('.$this->compileSelect($where['query']).')'; } /** * Compile a where exists clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNotExists(Builder $query, $where) { return 'not exists ('.$this->compileSelect($where['query']).')'; } /** * Compile a where row values condition. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereRowValues(Builder $query, $where) { $columns = $this->columnize($where['columns']); $values = $this->parameterize($where['values']); return '('.$columns.') '.$where['operator'].' ('.$values.')'; } /** * Compile a "where JSON boolean" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereJsonBoolean(Builder $query, $where) { $column = $this->wrapJsonBooleanSelector($where['column']); $value = $this->wrapJsonBooleanValue( $this->parameter($where['value']) ); return $column.' '.$where['operator'].' '.$value; } /** * Compile a "where JSON contains" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereJsonContains(Builder $query, $where) { $not = $where['not'] ? 'not ' : ''; return $not.$this->compileJsonContains( $where['column'], $this->parameter($where['value']) ); } /** * Compile a "JSON contains" statement into SQL. * * @param string $column * @param string $value * @return string * * @throws \RuntimeException */ protected function compileJsonContains($column, $value) { throw new RuntimeException('This database engine does not support JSON contains operations.'); } /** * Prepare the binding for a "JSON contains" statement. * * @param mixed $binding * @return string */ public function prepareBindingForJsonContains($binding) { return json_encode($binding, JSON_UNESCAPED_UNICODE); } /** * Compile a "where JSON contains key" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereJsonContainsKey(Builder $query, $where) { $not = $where['not'] ? 'not ' : ''; return $not.$this->compileJsonContainsKey( $where['column'] ); } /** * Compile a "JSON contains key" statement into SQL. * * @param string $column * @return string * * @throws \RuntimeException */ protected function compileJsonContainsKey($column) { throw new RuntimeException('This database engine does not support JSON contains key operations.'); } /** * Compile a "where JSON length" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereJsonLength(Builder $query, $where) { return $this->compileJsonLength( $where['column'], $where['operator'], $this->parameter($where['value']) ); } /** * Compile a "JSON length" statement into SQL. * * @param string $column * @param string $operator * @param string $value * @return string * * @throws \RuntimeException */ protected function compileJsonLength($column, $operator, $value) { throw new RuntimeException('This database engine does not support JSON length operations.'); } /** * Compile a "JSON value cast" statement into SQL. * * @param string $value * @return string */ public function compileJsonValueCast($value) { return $value; } /** * Compile a "where fulltext" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ public function whereFullText(Builder $query, $where) { throw new RuntimeException('This database engine does not support fulltext search operations.'); } /** * Compile a clause based on an expression. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ public function whereExpression(Builder $query, $where) { return $where['column']->getValue($this); } /** * Compile the "group by" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $groups * @return string */ protected function compileGroups(Builder $query, $groups) { return 'group by '.$this->columnize($groups); } /** * Compile the "having" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileHavings(Builder $query) { return 'having '.$this->removeLeadingBoolean(collect($query->havings)->map(function ($having) { return $having['boolean'].' '.$this->compileHaving($having); })->implode(' ')); } /** * Compile a single having clause. * * @param array $having * @return string */ protected function compileHaving(array $having) { // If the having clause is "raw", we can just return the clause straight away // without doing any more processing on it. Otherwise, we will compile the // clause into SQL based on the components that make it up from builder. return match ($having['type']) { 'Raw' => $having['sql'], 'between' => $this->compileHavingBetween($having), 'Null' => $this->compileHavingNull($having), 'NotNull' => $this->compileHavingNotNull($having), 'bit' => $this->compileHavingBit($having), 'Expression' => $this->compileHavingExpression($having), 'Nested' => $this->compileNestedHavings($having), default => $this->compileBasicHaving($having), }; } /** * Compile a basic having clause. * * @param array $having * @return string */ protected function compileBasicHaving($having) { $column = $this->wrap($having['column']); $parameter = $this->parameter($having['value']); return $column.' '.$having['operator'].' '.$parameter; } /** * Compile a "between" having clause. * * @param array $having * @return string */ protected function compileHavingBetween($having) { $between = $having['not'] ? 'not between' : 'between'; $column = $this->wrap($having['column']); $min = $this->parameter(head($having['values'])); $max = $this->parameter(last($having['values'])); return $column.' '.$between.' '.$min.' and '.$max; } /** * Compile a having null clause. * * @param array $having * @return string */ protected function compileHavingNull($having) { $column = $this->wrap($having['column']); return $column.' is null'; } /** * Compile a having not null clause. * * @param array $having * @return string */ protected function compileHavingNotNull($having) { $column = $this->wrap($having['column']); return $column.' is not null'; } /** * Compile a having clause involving a bit operator. * * @param array $having * @return string */ protected function compileHavingBit($having) { $column = $this->wrap($having['column']); $parameter = $this->parameter($having['value']); return '('.$column.' '.$having['operator'].' '.$parameter.') != 0'; } /** * Compile a having clause involving an expression. * * @param array $having * @return string */ protected function compileHavingExpression($having) { return $having['column']->getValue($this); } /** * Compile a nested having clause. * * @param array $having * @return string */ protected function compileNestedHavings($having) { return '('.substr($this->compileHavings($having['query']), 7).')'; } /** * Compile the "order by" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $orders * @return string */ protected function compileOrders(Builder $query, $orders) { if (! empty($orders)) { return 'order by '.implode(', ', $this->compileOrdersToArray($query, $orders)); } return ''; } /** * Compile the query orders to an array. * * @param \Illuminate\Database\Query\Builder $query * @param array $orders * @return array */ protected function compileOrdersToArray(Builder $query, $orders) { return array_map(function ($order) { return $order['sql'] ?? $this->wrap($order['column']).' '.$order['direction']; }, $orders); } /** * Compile the random statement into SQL. * * @param string|int $seed * @return string */ public function compileRandom($seed) { return 'RANDOM()'; } /** * Compile the "limit" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param int $limit * @return string */ protected function compileLimit(Builder $query, $limit) { return 'limit '.(int) $limit; } /** * Compile the "offset" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param int $offset * @return string */ protected function compileOffset(Builder $query, $offset) { return 'offset '.(int) $offset; } /** * Compile the "union" queries attached to the main query. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileUnions(Builder $query) { $sql = ''; foreach ($query->unions as $union) { $sql .= $this->compileUnion($union); } if (! empty($query->unionOrders)) { $sql .= ' '.$this->compileOrders($query, $query->unionOrders); } if (isset($query->unionLimit)) { $sql .= ' '.$this->compileLimit($query, $query->unionLimit); } if (isset($query->unionOffset)) { $sql .= ' '.$this->compileOffset($query, $query->unionOffset); } return ltrim($sql); } /** * Compile a single union statement. * * @param array $union * @return string */ protected function compileUnion(array $union) { $conjunction = $union['all'] ? ' union all ' : ' union '; return $conjunction.$this->wrapUnion($union['query']->toSql()); } /** * Wrap a union subquery in parentheses. * * @param string $sql * @return string */ protected function wrapUnion($sql) { return '('.$sql.')'; } /** * Compile a union aggregate query into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileUnionAggregate(Builder $query) { $sql = $this->compileAggregate($query, $query->aggregate); $query->aggregate = null; return $sql.' from ('.$this->compileSelect($query).') as '.$this->wrapTable('temp_table'); } /** * Compile an exists statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileExists(Builder $query) { $select = $this->compileSelect($query); return "select exists({$select}) as {$this->wrap('exists')}"; } /** * Compile an insert statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileInsert(Builder $query, array $values) { // Essentially we will force every insert to be treated as a batch insert which // simply makes creating the SQL easier for us since we can utilize the same // basic routine regardless of an amount of records given to us to insert. $table = $this->wrapTable($query->from); if (empty($values)) { return "insert into {$table} default values"; } if (! is_array(reset($values))) { $values = [$values]; } $columns = $this->columnize(array_keys(reset($values))); // We need to build a list of parameter place-holders of values that are bound // to the query. Each insert should have the exact same number of parameter // bindings so we will loop through the record and parameterize them all. $parameters = collect($values)->map(function ($record) { return '('.$this->parameterize($record).')'; })->implode(', '); return "insert into $table ($columns) values $parameters"; } /** * Compile an insert ignore statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string * * @throws \RuntimeException */ public function compileInsertOrIgnore(Builder $query, array $values) { throw new RuntimeException('This database engine does not support inserting while ignoring errors.'); } /** * Compile an insert and get ID statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param string $sequence * @return string */ public function compileInsertGetId(Builder $query, $values, $sequence) { return $this->compileInsert($query, $values); } /** * Compile an insert statement using a subquery into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $columns * @param string $sql * @return string */ public function compileInsertUsing(Builder $query, array $columns, string $sql) { $table = $this->wrapTable($query->from); if (empty($columns) || $columns === ['*']) { return "insert into {$table} $sql"; } return "insert into {$table} ({$this->columnize($columns)}) $sql"; } /** * Compile an update statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileUpdate(Builder $query, array $values) { $table = $this->wrapTable($query->from); $columns = $this->compileUpdateColumns($query, $values); $where = $this->compileWheres($query); return trim( isset($query->joins) ? $this->compileUpdateWithJoins($query, $table, $columns, $where) : $this->compileUpdateWithoutJoins($query, $table, $columns, $where) ); } /** * Compile the columns for an update statement. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ protected function compileUpdateColumns(Builder $query, array $values) { return collect($values)->map(function ($value, $key) { return $this->wrap($key).' = '.$this->parameter($value); })->implode(', '); } /** * Compile an update statement without joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $columns * @param string $where * @return string */ protected function compileUpdateWithoutJoins(Builder $query, $table, $columns, $where) { return "update {$table} set {$columns} {$where}"; } /** * Compile an update statement with joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $columns * @param string $where * @return string */ protected function compileUpdateWithJoins(Builder $query, $table, $columns, $where) { $joins = $this->compileJoins($query, $query->joins); return "update {$table} {$joins} set {$columns} {$where}"; } /** * Compile an "upsert" statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param array $uniqueBy * @param array $update * @return string * * @throws \RuntimeException */ public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) { throw new RuntimeException('This database engine does not support upserts.'); } /** * Prepare the bindings for an update statement. * * @param array $bindings * @param array $values * @return array */ public function prepareBindingsForUpdate(array $bindings, array $values) { $cleanBindings = Arr::except($bindings, ['select', 'join']); return array_values( array_merge($bindings['join'], $values, Arr::flatten($cleanBindings)) ); } /** * Compile a delete statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileDelete(Builder $query) { $table = $this->wrapTable($query->from); $where = $this->compileWheres($query); return trim( isset($query->joins) ? $this->compileDeleteWithJoins($query, $table, $where) : $this->compileDeleteWithoutJoins($query, $table, $where) ); } /** * Compile a delete statement without joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $where * @return string */ protected function compileDeleteWithoutJoins(Builder $query, $table, $where) { return "delete from {$table} {$where}"; } /** * Compile a delete statement with joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $where * @return string */ protected function compileDeleteWithJoins(Builder $query, $table, $where) { $alias = last(explode(' as ', $table)); $joins = $this->compileJoins($query, $query->joins); return "delete {$alias} from {$table} {$joins} {$where}"; } /** * Prepare the bindings for a delete statement. * * @param array $bindings * @return array */ public function prepareBindingsForDelete(array $bindings) { return Arr::flatten( Arr::except($bindings, 'select') ); } /** * Compile a truncate table statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return array */ public function compileTruncate(Builder $query) { return ['truncate table '.$this->wrapTable($query->from) => []]; } /** * Compile the lock into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param bool|string $value * @return string */ protected function compileLock(Builder $query, $value) { return is_string($value) ? $value : ''; } /** * Determine if the grammar supports savepoints. * * @return bool */ public function supportsSavepoints() { return true; } /** * Compile the SQL statement to define a savepoint. * * @param string $name * @return string */ public function compileSavepoint($name) { return 'SAVEPOINT '.$name; } /** * Compile the SQL statement to execute a savepoint rollback. * * @param string $name * @return string */ public function compileSavepointRollBack($name) { return 'ROLLBACK TO SAVEPOINT '.$name; } /** * Wrap the given JSON selector for boolean values. * * @param string $value * @return string */ protected function wrapJsonBooleanSelector($value) { return $this->wrapJsonSelector($value); } /** * Wrap the given JSON boolean value. * * @param string $value * @return string */ protected function wrapJsonBooleanValue($value) { return $value; } /** * Concatenate an array of segments, removing empties. * * @param array $segments * @return string */ protected function concatenate($segments) { return implode(' ', array_filter($segments, function ($value) { return (string) $value !== ''; })); } /** * Remove the leading boolean from a statement. * * @param string $value * @return string */ protected function removeLeadingBoolean($value) { return preg_replace('/and |or /i', '', $value, 1); } /** * Substitute the given bindings into the given raw SQL query. * * @param string $sql * @param array $bindings * @return string */ public function substituteBindingsIntoRawSql($sql, $bindings) { $bindings = array_map(fn ($value) => $this->escape($value), $bindings); $query = ''; $isStringLiteral = false; for ($i = 0; $i < strlen($sql); $i++) { $char = $sql[$i]; $nextChar = $sql[$i + 1] ?? null; // Single quotes can be escaped as '' according to the SQL standard while // MySQL uses \'. Postgres has operators like ?| that must get encoded // in PHP like ??|. We should skip over the escaped characters here. if (in_array($char.$nextChar, ["\'", "''", '??'])) { $query .= $char.$nextChar; $i += 1; } elseif ($char === "'") { // Starting / leaving string literal... $query .= $char; $isStringLiteral = ! $isStringLiteral; } elseif ($char === '?' && ! $isStringLiteral) { // Substitutable binding... $query .= array_shift($bindings) ?? '?'; } else { // Normal character... $query .= $char; } } return $query; } /** * Get the grammar specific operators. * * @return array */ public function getOperators() { return $this->operators; } /** * Get the grammar specific bitwise operators. * * @return array */ public function getBitwiseOperators() { return $this->bitwiseOperators; } } Query/Grammars/MySqlGrammar.php000075500000025262147536122140012526 0ustar00getValue($where['column']); if ($this->isJsonSelector($columnValue)) { [$field, $path] = $this->wrapJsonFieldAndPath($columnValue); return '(json_extract('.$field.$path.') is null OR json_type(json_extract('.$field.$path.')) = \'NULL\')'; } return parent::whereNull($query, $where); } /** * Add a "where not null" clause to the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereNotNull(Builder $query, $where) { $columnValue = (string) $this->getValue($where['column']); if ($this->isJsonSelector($columnValue)) { [$field, $path] = $this->wrapJsonFieldAndPath($columnValue); return '(json_extract('.$field.$path.') is not null AND json_type(json_extract('.$field.$path.')) != \'NULL\')'; } return parent::whereNotNull($query, $where); } /** * Compile a "where fulltext" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ public function whereFullText(Builder $query, $where) { $columns = $this->columnize($where['columns']); $value = $this->parameter($where['value']); $mode = ($where['options']['mode'] ?? []) === 'boolean' ? ' in boolean mode' : ' in natural language mode'; $expanded = ($where['options']['expanded'] ?? []) && ($where['options']['mode'] ?? []) !== 'boolean' ? ' with query expansion' : ''; return "match ({$columns}) against (".$value."{$mode}{$expanded})"; } /** * Compile the index hints for the query. * * @param \Illuminate\Database\Query\Builder $query * @param \Illuminate\Database\Query\IndexHint $indexHint * @return string */ protected function compileIndexHint(Builder $query, $indexHint) { return match ($indexHint->type) { 'hint' => "use index ({$indexHint->index})", 'force' => "force index ({$indexHint->index})", default => "ignore index ({$indexHint->index})", }; } /** * Compile an insert ignore statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileInsertOrIgnore(Builder $query, array $values) { return Str::replaceFirst('insert', 'insert ignore', $this->compileInsert($query, $values)); } /** * Compile a "JSON contains" statement into SQL. * * @param string $column * @param string $value * @return string */ protected function compileJsonContains($column, $value) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return 'json_contains('.$field.', '.$value.$path.')'; } /** * Compile a "JSON contains key" statement into SQL. * * @param string $column * @return string */ protected function compileJsonContainsKey($column) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return 'ifnull(json_contains_path('.$field.', \'one\''.$path.'), 0)'; } /** * Compile a "JSON length" statement into SQL. * * @param string $column * @param string $operator * @param string $value * @return string */ protected function compileJsonLength($column, $operator, $value) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return 'json_length('.$field.$path.') '.$operator.' '.$value; } /** * Compile a "JSON value cast" statement into SQL. * * @param string $value * @return string */ public function compileJsonValueCast($value) { return 'cast('.$value.' as json)'; } /** * Compile the random statement into SQL. * * @param string|int $seed * @return string */ public function compileRandom($seed) { return 'RAND('.$seed.')'; } /** * Compile the lock into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param bool|string $value * @return string */ protected function compileLock(Builder $query, $value) { if (! is_string($value)) { return $value ? 'for update' : 'lock in share mode'; } return $value; } /** * Compile an insert statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileInsert(Builder $query, array $values) { if (empty($values)) { $values = [[]]; } return parent::compileInsert($query, $values); } /** * Compile the columns for an update statement. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ protected function compileUpdateColumns(Builder $query, array $values) { return collect($values)->map(function ($value, $key) { if ($this->isJsonSelector($key)) { return $this->compileJsonUpdateColumn($key, $value); } return $this->wrap($key).' = '.$this->parameter($value); })->implode(', '); } /** * Compile an "upsert" statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param array $uniqueBy * @param array $update * @return string */ public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) { $useUpsertAlias = $query->connection->getConfig('use_upsert_alias'); $sql = $this->compileInsert($query, $values); if ($useUpsertAlias) { $sql .= ' as laravel_upsert_alias'; } $sql .= ' on duplicate key update '; $columns = collect($update)->map(function ($value, $key) use ($useUpsertAlias) { if (! is_numeric($key)) { return $this->wrap($key).' = '.$this->parameter($value); } return $useUpsertAlias ? $this->wrap($value).' = '.$this->wrap('laravel_upsert_alias').'.'.$this->wrap($value) : $this->wrap($value).' = values('.$this->wrap($value).')'; })->implode(', '); return $sql.$columns; } /** * Prepare a JSON column being updated using the JSON_SET function. * * @param string $key * @param mixed $value * @return string */ protected function compileJsonUpdateColumn($key, $value) { if (is_bool($value)) { $value = $value ? 'true' : 'false'; } elseif (is_array($value)) { $value = 'cast(? as json)'; } else { $value = $this->parameter($value); } [$field, $path] = $this->wrapJsonFieldAndPath($key); return "{$field} = json_set({$field}{$path}, {$value})"; } /** * Compile an update statement without joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $columns * @param string $where * @return string */ protected function compileUpdateWithoutJoins(Builder $query, $table, $columns, $where) { $sql = parent::compileUpdateWithoutJoins($query, $table, $columns, $where); if (! empty($query->orders)) { $sql .= ' '.$this->compileOrders($query, $query->orders); } if (isset($query->limit)) { $sql .= ' '.$this->compileLimit($query, $query->limit); } return $sql; } /** * Prepare the bindings for an update statement. * * Booleans, integers, and doubles are inserted into JSON updates as raw values. * * @param array $bindings * @param array $values * @return array */ public function prepareBindingsForUpdate(array $bindings, array $values) { $values = collect($values)->reject(function ($value, $column) { return $this->isJsonSelector($column) && is_bool($value); })->map(function ($value) { return is_array($value) ? json_encode($value) : $value; })->all(); return parent::prepareBindingsForUpdate($bindings, $values); } /** * Compile a delete query that does not use joins. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $where * @return string */ protected function compileDeleteWithoutJoins(Builder $query, $table, $where) { $sql = parent::compileDeleteWithoutJoins($query, $table, $where); // When using MySQL, delete statements may contain order by statements and limits // so we will compile both of those here. Once we have finished compiling this // we will return the completed SQL statement so it will be executed for us. if (! empty($query->orders)) { $sql .= ' '.$this->compileOrders($query, $query->orders); } if (isset($query->limit)) { $sql .= ' '.$this->compileLimit($query, $query->limit); } return $sql; } /** * Wrap a single string in keyword identifiers. * * @param string $value * @return string */ protected function wrapValue($value) { return $value === '*' ? $value : '`'.str_replace('`', '``', $value).'`'; } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_unquote(json_extract('.$field.$path.'))'; } /** * Wrap the given JSON selector for boolean values. * * @param string $value * @return string */ protected function wrapJsonBooleanSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_extract('.$field.$path.')'; } } Query/Grammars/PostgresGrammar.php000075500000050710147536122140013263 0ustar00', '<=', '>=', '<>', '!=', 'like', 'not like', 'between', 'ilike', 'not ilike', '~', '&', '|', '#', '<<', '>>', '<<=', '>>=', '&&', '@>', '<@', '?', '?|', '?&', '||', '-', '@?', '@@', '#-', 'is distinct from', 'is not distinct from', ]; /** * The grammar specific bitwise operators. * * @var array */ protected $bitwiseOperators = [ '~', '&', '|', '#', '<<', '>>', '<<=', '>>=', ]; /** * Compile a basic where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBasic(Builder $query, $where) { if (str_contains(strtolower($where['operator']), 'like')) { return sprintf( '%s::text %s %s', $this->wrap($where['column']), $where['operator'], $this->parameter($where['value']) ); } return parent::whereBasic($query, $where); } /** * Compile a bitwise operator where clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBitwise(Builder $query, $where) { $value = $this->parameter($where['value']); $operator = str_replace('?', '??', $where['operator']); return '('.$this->wrap($where['column']).' '.$operator.' '.$value.')::bool'; } /** * Compile a "where date" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereDate(Builder $query, $where) { $value = $this->parameter($where['value']); return $this->wrap($where['column']).'::date '.$where['operator'].' '.$value; } /** * Compile a "where time" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereTime(Builder $query, $where) { $value = $this->parameter($where['value']); return $this->wrap($where['column']).'::time '.$where['operator'].' '.$value; } /** * Compile a date based where clause. * * @param string $type * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function dateBasedWhere($type, Builder $query, $where) { $value = $this->parameter($where['value']); return 'extract('.$type.' from '.$this->wrap($where['column']).') '.$where['operator'].' '.$value; } /** * Compile a "where fulltext" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ public function whereFullText(Builder $query, $where) { $language = $where['options']['language'] ?? 'english'; if (! in_array($language, $this->validFullTextLanguages())) { $language = 'english'; } $columns = collect($where['columns'])->map(function ($column) use ($language) { return "to_tsvector('{$language}', {$this->wrap($column)})"; })->implode(' || '); $mode = 'plainto_tsquery'; if (($where['options']['mode'] ?? []) === 'phrase') { $mode = 'phraseto_tsquery'; } if (($where['options']['mode'] ?? []) === 'websearch') { $mode = 'websearch_to_tsquery'; } return "({$columns}) @@ {$mode}('{$language}', {$this->parameter($where['value'])})"; } /** * Get an array of valid full text languages. * * @return array */ protected function validFullTextLanguages() { return [ 'simple', 'arabic', 'danish', 'dutch', 'english', 'finnish', 'french', 'german', 'hungarian', 'indonesian', 'irish', 'italian', 'lithuanian', 'nepali', 'norwegian', 'portuguese', 'romanian', 'russian', 'spanish', 'swedish', 'tamil', 'turkish', ]; } /** * Compile the "select *" portion of the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $columns * @return string|null */ protected function compileColumns(Builder $query, $columns) { // If the query is actually performing an aggregating select, we will let that // compiler handle the building of the select clauses, as it will need some // more syntax that is best handled by that function to keep things neat. if (! is_null($query->aggregate)) { return; } if (is_array($query->distinct)) { $select = 'select distinct on ('.$this->columnize($query->distinct).') '; } elseif ($query->distinct) { $select = 'select distinct '; } else { $select = 'select '; } return $select.$this->columnize($columns); } /** * Compile a "JSON contains" statement into SQL. * * @param string $column * @param string $value * @return string */ protected function compileJsonContains($column, $value) { $column = str_replace('->>', '->', $this->wrap($column)); return '('.$column.')::jsonb @> '.$value; } /** * Compile a "JSON contains key" statement into SQL. * * @param string $column * @return string */ protected function compileJsonContainsKey($column) { $segments = explode('->', $column); $lastSegment = array_pop($segments); if (filter_var($lastSegment, FILTER_VALIDATE_INT) !== false) { $i = $lastSegment; } elseif (preg_match('/\[(-?[0-9]+)\]$/', $lastSegment, $matches)) { $segments[] = Str::beforeLast($lastSegment, $matches[0]); $i = $matches[1]; } $column = str_replace('->>', '->', $this->wrap(implode('->', $segments))); if (isset($i)) { return vsprintf('case when %s then %s else false end', [ 'jsonb_typeof(('.$column.")::jsonb) = 'array'", 'jsonb_array_length(('.$column.')::jsonb) >= '.($i < 0 ? abs($i) : $i + 1), ]); } $key = "'".str_replace("'", "''", $lastSegment)."'"; return 'coalesce(('.$column.')::jsonb ?? '.$key.', false)'; } /** * Compile a "JSON length" statement into SQL. * * @param string $column * @param string $operator * @param string $value * @return string */ protected function compileJsonLength($column, $operator, $value) { $column = str_replace('->>', '->', $this->wrap($column)); return 'jsonb_array_length(('.$column.')::jsonb) '.$operator.' '.$value; } /** * Compile a single having clause. * * @param array $having * @return string */ protected function compileHaving(array $having) { if ($having['type'] === 'Bitwise') { return $this->compileHavingBitwise($having); } return parent::compileHaving($having); } /** * Compile a having clause involving a bitwise operator. * * @param array $having * @return string */ protected function compileHavingBitwise($having) { $column = $this->wrap($having['column']); $parameter = $this->parameter($having['value']); return '('.$column.' '.$having['operator'].' '.$parameter.')::bool'; } /** * Compile the lock into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param bool|string $value * @return string */ protected function compileLock(Builder $query, $value) { if (! is_string($value)) { return $value ? 'for update' : 'for share'; } return $value; } /** * Compile an insert ignore statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileInsertOrIgnore(Builder $query, array $values) { return $this->compileInsert($query, $values).' on conflict do nothing'; } /** * Compile an insert and get ID statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param string $sequence * @return string */ public function compileInsertGetId(Builder $query, $values, $sequence) { return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence ?: 'id'); } /** * Compile an update statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileUpdate(Builder $query, array $values) { if (isset($query->joins) || isset($query->limit)) { return $this->compileUpdateWithJoinsOrLimit($query, $values); } return parent::compileUpdate($query, $values); } /** * Compile the columns for an update statement. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ protected function compileUpdateColumns(Builder $query, array $values) { return collect($values)->map(function ($value, $key) { $column = last(explode('.', $key)); if ($this->isJsonSelector($key)) { return $this->compileJsonUpdateColumn($column, $value); } return $this->wrap($column).' = '.$this->parameter($value); })->implode(', '); } /** * Compile an "upsert" statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param array $uniqueBy * @param array $update * @return string */ public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) { $sql = $this->compileInsert($query, $values); $sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set '; $columns = collect($update)->map(function ($value, $key) { return is_numeric($key) ? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value) : $this->wrap($key).' = '.$this->parameter($value); })->implode(', '); return $sql.$columns; } /** * Prepares a JSON column being updated using the JSONB_SET function. * * @param string $key * @param mixed $value * @return string */ protected function compileJsonUpdateColumn($key, $value) { $segments = explode('->', $key); $field = $this->wrap(array_shift($segments)); $path = "'{".implode(',', $this->wrapJsonPathAttributes($segments, '"'))."}'"; return "{$field} = jsonb_set({$field}::jsonb, {$path}, {$this->parameter($value)})"; } /** * Compile an update from statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileUpdateFrom(Builder $query, $values) { $table = $this->wrapTable($query->from); // Each one of the columns in the update statements needs to be wrapped in the // keyword identifiers, also a place-holder needs to be created for each of // the values in the list of bindings so we can make the sets statements. $columns = $this->compileUpdateColumns($query, $values); $from = ''; if (isset($query->joins)) { // When using Postgres, updates with joins list the joined tables in the from // clause, which is different than other systems like MySQL. Here, we will // compile out the tables that are joined and add them to a from clause. $froms = collect($query->joins)->map(function ($join) { return $this->wrapTable($join->table); })->all(); if (count($froms) > 0) { $from = ' from '.implode(', ', $froms); } } $where = $this->compileUpdateWheres($query); return trim("update {$table} set {$columns}{$from} {$where}"); } /** * Compile the additional where clauses for updates with joins. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileUpdateWheres(Builder $query) { $baseWheres = $this->compileWheres($query); if (! isset($query->joins)) { return $baseWheres; } // Once we compile the join constraints, we will either use them as the where // clause or append them to the existing base where clauses. If we need to // strip the leading boolean we will do so when using as the only where. $joinWheres = $this->compileUpdateJoinWheres($query); if (trim($baseWheres) == '') { return 'where '.$this->removeLeadingBoolean($joinWheres); } return $baseWheres.' '.$joinWheres; } /** * Compile the "join" clause where clauses for an update. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileUpdateJoinWheres(Builder $query) { $joinWheres = []; // Here we will just loop through all of the join constraints and compile them // all out then implode them. This should give us "where" like syntax after // everything has been built and then we will join it to the real wheres. foreach ($query->joins as $join) { foreach ($join->wheres as $where) { $method = "where{$where['type']}"; $joinWheres[] = $where['boolean'].' '.$this->$method($query, $where); } } return implode(' ', $joinWheres); } /** * Prepare the bindings for an update statement. * * @param array $bindings * @param array $values * @return array */ public function prepareBindingsForUpdateFrom(array $bindings, array $values) { $values = collect($values)->map(function ($value, $column) { return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value)) ? json_encode($value) : $value; })->all(); $bindingsWithoutWhere = Arr::except($bindings, ['select', 'where']); return array_values( array_merge($values, $bindings['where'], Arr::flatten($bindingsWithoutWhere)) ); } /** * Compile an update statement with joins or limit into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ protected function compileUpdateWithJoinsOrLimit(Builder $query, array $values) { $table = $this->wrapTable($query->from); $columns = $this->compileUpdateColumns($query, $values); $alias = last(preg_split('/\s+as\s+/i', $query->from)); $selectSql = $this->compileSelect($query->select($alias.'.ctid')); return "update {$table} set {$columns} where {$this->wrap('ctid')} in ({$selectSql})"; } /** * Prepare the bindings for an update statement. * * @param array $bindings * @param array $values * @return array */ public function prepareBindingsForUpdate(array $bindings, array $values) { $values = collect($values)->map(function ($value, $column) { return is_array($value) || ($this->isJsonSelector($column) && ! $this->isExpression($value)) ? json_encode($value) : $value; })->all(); $cleanBindings = Arr::except($bindings, 'select'); return array_values( array_merge($values, Arr::flatten($cleanBindings)) ); } /** * Compile a delete statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileDelete(Builder $query) { if (isset($query->joins) || isset($query->limit)) { return $this->compileDeleteWithJoinsOrLimit($query); } return parent::compileDelete($query); } /** * Compile a delete statement with joins or limit into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileDeleteWithJoinsOrLimit(Builder $query) { $table = $this->wrapTable($query->from); $alias = last(preg_split('/\s+as\s+/i', $query->from)); $selectSql = $this->compileSelect($query->select($alias.'.ctid')); return "delete from {$table} where {$this->wrap('ctid')} in ({$selectSql})"; } /** * Compile a truncate table statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return array */ public function compileTruncate(Builder $query) { return ['truncate '.$this->wrapTable($query->from).' restart identity cascade' => []]; } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { $path = explode('->', $value); $field = $this->wrapSegments(explode('.', array_shift($path))); $wrappedPath = $this->wrapJsonPathAttributes($path); $attribute = array_pop($wrappedPath); if (! empty($wrappedPath)) { return $field.'->'.implode('->', $wrappedPath).'->>'.$attribute; } return $field.'->>'.$attribute; } /** * Wrap the given JSON selector for boolean values. * * @param string $value * @return string */ protected function wrapJsonBooleanSelector($value) { $selector = str_replace( '->>', '->', $this->wrapJsonSelector($value) ); return '('.$selector.')::jsonb'; } /** * Wrap the given JSON boolean value. * * @param string $value * @return string */ protected function wrapJsonBooleanValue($value) { return "'".$value."'::jsonb"; } /** * Wrap the attributes of the given JSON path. * * @param array $path * @return array */ protected function wrapJsonPathAttributes($path) { $quote = func_num_args() === 2 ? func_get_arg(1) : "'"; return collect($path)->map(function ($attribute) { return $this->parseJsonPathArrayKeys($attribute); })->collapse()->map(function ($attribute) use ($quote) { return filter_var($attribute, FILTER_VALIDATE_INT) !== false ? $attribute : $quote.$attribute.$quote; })->all(); } /** * Parse the given JSON path attribute for array keys. * * @param string $attribute * @return array */ protected function parseJsonPathArrayKeys($attribute) { if (preg_match('/(\[[^\]]+\])+$/', $attribute, $parts)) { $key = Str::beforeLast($attribute, $parts[0]); preg_match_all('/\[([^\]]+)\]/', $parts[0], $keys); return collect([$key]) ->merge($keys[1]) ->diff('') ->values() ->all(); } return [$attribute]; } /** * Substitute the given bindings into the given raw SQL query. * * @param string $sql * @param array $bindings * @return string */ public function substituteBindingsIntoRawSql($sql, $bindings) { $query = parent::substituteBindingsIntoRawSql($sql, $bindings); foreach ($this->operators as $operator) { if (! str_contains($operator, '?')) { continue; } $query = str_replace(str_replace('?', '??', $operator), $operator, $query); } return $query; } } Query/Grammars/SQLiteGrammar.php000075500000023722147536122140012621 0ustar00', '<=', '>=', '<>', '!=', 'like', 'not like', 'ilike', '&', '|', '<<', '>>', ]; /** * Compile the lock into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param bool|string $value * @return string */ protected function compileLock(Builder $query, $value) { return ''; } /** * Wrap a union subquery in parentheses. * * @param string $sql * @return string */ protected function wrapUnion($sql) { return 'select * from ('.$sql.')'; } /** * Compile a "where date" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereDate(Builder $query, $where) { return $this->dateBasedWhere('%Y-%m-%d', $query, $where); } /** * Compile a "where day" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereDay(Builder $query, $where) { return $this->dateBasedWhere('%d', $query, $where); } /** * Compile a "where month" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereMonth(Builder $query, $where) { return $this->dateBasedWhere('%m', $query, $where); } /** * Compile a "where year" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereYear(Builder $query, $where) { return $this->dateBasedWhere('%Y', $query, $where); } /** * Compile a "where time" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereTime(Builder $query, $where) { return $this->dateBasedWhere('%H:%M:%S', $query, $where); } /** * Compile a date based where clause. * * @param string $type * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function dateBasedWhere($type, Builder $query, $where) { $value = $this->parameter($where['value']); return "strftime('{$type}', {$this->wrap($where['column'])}) {$where['operator']} cast({$value} as text)"; } /** * Compile the index hints for the query. * * @param \Illuminate\Database\Query\Builder $query * @param \Illuminate\Database\Query\IndexHint $indexHint * @return string */ protected function compileIndexHint(Builder $query, $indexHint) { return $indexHint->type === 'force' ? "indexed by {$indexHint->index}" : ''; } /** * Compile a "JSON length" statement into SQL. * * @param string $column * @param string $operator * @param string $value * @return string */ protected function compileJsonLength($column, $operator, $value) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return 'json_array_length('.$field.$path.') '.$operator.' '.$value; } /** * Compile a "JSON contains key" statement into SQL. * * @param string $column * @return string */ protected function compileJsonContainsKey($column) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return 'json_type('.$field.$path.') is not null'; } /** * Compile an update statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileUpdate(Builder $query, array $values) { if (isset($query->joins) || isset($query->limit)) { return $this->compileUpdateWithJoinsOrLimit($query, $values); } return parent::compileUpdate($query, $values); } /** * Compile an insert ignore statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ public function compileInsertOrIgnore(Builder $query, array $values) { return Str::replaceFirst('insert', 'insert or ignore', $this->compileInsert($query, $values)); } /** * Compile the columns for an update statement. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ protected function compileUpdateColumns(Builder $query, array $values) { $jsonGroups = $this->groupJsonColumnsForUpdate($values); return collect($values)->reject(function ($value, $key) { return $this->isJsonSelector($key); })->merge($jsonGroups)->map(function ($value, $key) use ($jsonGroups) { $column = last(explode('.', $key)); $value = isset($jsonGroups[$key]) ? $this->compileJsonPatch($column, $value) : $this->parameter($value); return $this->wrap($column).' = '.$value; })->implode(', '); } /** * Compile an "upsert" statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param array $uniqueBy * @param array $update * @return string */ public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) { $sql = $this->compileInsert($query, $values); $sql .= ' on conflict ('.$this->columnize($uniqueBy).') do update set '; $columns = collect($update)->map(function ($value, $key) { return is_numeric($key) ? $this->wrap($value).' = '.$this->wrapValue('excluded').'.'.$this->wrap($value) : $this->wrap($key).' = '.$this->parameter($value); })->implode(', '); return $sql.$columns; } /** * Group the nested JSON columns. * * @param array $values * @return array */ protected function groupJsonColumnsForUpdate(array $values) { $groups = []; foreach ($values as $key => $value) { if ($this->isJsonSelector($key)) { Arr::set($groups, str_replace('->', '.', Str::after($key, '.')), $value); } } return $groups; } /** * Compile a "JSON" patch statement into SQL. * * @param string $column * @param mixed $value * @return string */ protected function compileJsonPatch($column, $value) { return "json_patch(ifnull({$this->wrap($column)}, json('{}')), json({$this->parameter($value)}))"; } /** * Compile an update statement with joins or limit into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @return string */ protected function compileUpdateWithJoinsOrLimit(Builder $query, array $values) { $table = $this->wrapTable($query->from); $columns = $this->compileUpdateColumns($query, $values); $alias = last(preg_split('/\s+as\s+/i', $query->from)); $selectSql = $this->compileSelect($query->select($alias.'.rowid')); return "update {$table} set {$columns} where {$this->wrap('rowid')} in ({$selectSql})"; } /** * Prepare the bindings for an update statement. * * @param array $bindings * @param array $values * @return array */ public function prepareBindingsForUpdate(array $bindings, array $values) { $groups = $this->groupJsonColumnsForUpdate($values); $values = collect($values)->reject(function ($value, $key) { return $this->isJsonSelector($key); })->merge($groups)->map(function ($value) { return is_array($value) ? json_encode($value) : $value; })->all(); $cleanBindings = Arr::except($bindings, 'select'); return array_values( array_merge($values, Arr::flatten($cleanBindings)) ); } /** * Compile a delete statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileDelete(Builder $query) { if (isset($query->joins) || isset($query->limit)) { return $this->compileDeleteWithJoinsOrLimit($query); } return parent::compileDelete($query); } /** * Compile a delete statement with joins or limit into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ protected function compileDeleteWithJoinsOrLimit(Builder $query) { $table = $this->wrapTable($query->from); $alias = last(preg_split('/\s+as\s+/i', $query->from)); $selectSql = $this->compileSelect($query->select($alias.'.rowid')); return "delete from {$table} where {$this->wrap('rowid')} in ({$selectSql})"; } /** * Compile a truncate table statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return array */ public function compileTruncate(Builder $query) { return [ 'delete from sqlite_sequence where name = ?' => [$query->from], 'delete from '.$this->wrapTable($query->from) => [], ]; } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_extract('.$field.$path.')'; } } Query/Grammars/SqlServerGrammar.php000075500000033476147536122140013415 0ustar00', '<=', '>=', '!<', '!>', '<>', '!=', 'like', 'not like', 'ilike', '&', '&=', '|', '|=', '^', '^=', ]; /** * The components that make up a select clause. * * @var string[] */ protected $selectComponents = [ 'aggregate', 'columns', 'from', 'indexHint', 'joins', 'wheres', 'groups', 'havings', 'orders', 'offset', 'limit', 'lock', ]; /** * Compile a select query into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileSelect(Builder $query) { // An order by clause is required for SQL Server offset to function... if ($query->offset && empty($query->orders)) { $query->orders[] = ['sql' => '(SELECT 0)']; } return parent::compileSelect($query); } /** * Compile the "select *" portion of the query. * * @param \Illuminate\Database\Query\Builder $query * @param array $columns * @return string|null */ protected function compileColumns(Builder $query, $columns) { if (! is_null($query->aggregate)) { return; } $select = $query->distinct ? 'select distinct ' : 'select '; // If there is a limit on the query, but not an offset, we will add the top // clause to the query, which serves as a "limit" type clause within the // SQL Server system similar to the limit keywords available in MySQL. if (is_numeric($query->limit) && $query->limit > 0 && $query->offset <= 0) { $select .= 'top '.((int) $query->limit).' '; } return $select.$this->columnize($columns); } /** * Compile the "from" portion of the query. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @return string */ protected function compileFrom(Builder $query, $table) { $from = parent::compileFrom($query, $table); if (is_string($query->lock)) { return $from.' '.$query->lock; } if (! is_null($query->lock)) { return $from.' with(rowlock,'.($query->lock ? 'updlock,' : '').'holdlock)'; } return $from; } /** * Compile the index hints for the query. * * @param \Illuminate\Database\Query\Builder $query * @param \Illuminate\Database\Query\IndexHint $indexHint * @return string */ protected function compileIndexHint(Builder $query, $indexHint) { return $indexHint->type === 'force' ? "with (index({$indexHint->index}))" : ''; } /** * {@inheritdoc} * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereBitwise(Builder $query, $where) { $value = $this->parameter($where['value']); $operator = str_replace('?', '??', $where['operator']); return '('.$this->wrap($where['column']).' '.$operator.' '.$value.') != 0'; } /** * Compile a "where date" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereDate(Builder $query, $where) { $value = $this->parameter($where['value']); return 'cast('.$this->wrap($where['column']).' as date) '.$where['operator'].' '.$value; } /** * Compile a "where time" clause. * * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function whereTime(Builder $query, $where) { $value = $this->parameter($where['value']); return 'cast('.$this->wrap($where['column']).' as time) '.$where['operator'].' '.$value; } /** * Compile a "JSON contains" statement into SQL. * * @param string $column * @param string $value * @return string */ protected function compileJsonContains($column, $value) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return $value.' in (select [value] from openjson('.$field.$path.'))'; } /** * Prepare the binding for a "JSON contains" statement. * * @param mixed $binding * @return string */ public function prepareBindingForJsonContains($binding) { return is_bool($binding) ? json_encode($binding) : $binding; } /** * Compile a "JSON contains key" statement into SQL. * * @param string $column * @return string */ protected function compileJsonContainsKey($column) { $segments = explode('->', $column); $lastSegment = array_pop($segments); if (preg_match('/\[([0-9]+)\]$/', $lastSegment, $matches)) { $segments[] = Str::beforeLast($lastSegment, $matches[0]); $key = $matches[1]; } else { $key = "'".str_replace("'", "''", $lastSegment)."'"; } [$field, $path] = $this->wrapJsonFieldAndPath(implode('->', $segments)); return $key.' in (select [key] from openjson('.$field.$path.'))'; } /** * Compile a "JSON length" statement into SQL. * * @param string $column * @param string $operator * @param string $value * @return string */ protected function compileJsonLength($column, $operator, $value) { [$field, $path] = $this->wrapJsonFieldAndPath($column); return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value; } /** * Compile a "JSON value cast" statement into SQL. * * @param string $value * @return string */ public function compileJsonValueCast($value) { return 'json_query('.$value.')'; } /** * Compile a single having clause. * * @param array $having * @return string */ protected function compileHaving(array $having) { if ($having['type'] === 'Bitwise') { return $this->compileHavingBitwise($having); } return parent::compileHaving($having); } /** * Compile a having clause involving a bitwise operator. * * @param array $having * @return string */ protected function compileHavingBitwise($having) { $column = $this->wrap($having['column']); $parameter = $this->parameter($having['value']); return '('.$column.' '.$having['operator'].' '.$parameter.') != 0'; } /** * Compile a delete statement without joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $where * @return string */ protected function compileDeleteWithoutJoins(Builder $query, $table, $where) { $sql = parent::compileDeleteWithoutJoins($query, $table, $where); return ! is_null($query->limit) && $query->limit > 0 && $query->offset <= 0 ? Str::replaceFirst('delete', 'delete top ('.$query->limit.')', $sql) : $sql; } /** * Compile the random statement into SQL. * * @param string|int $seed * @return string */ public function compileRandom($seed) { return 'NEWID()'; } /** * Compile the "limit" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param int $limit * @return string */ protected function compileLimit(Builder $query, $limit) { $limit = (int) $limit; if ($limit && $query->offset > 0) { return "fetch next {$limit} rows only"; } return ''; } /** * Compile the "offset" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param int $offset * @return string */ protected function compileOffset(Builder $query, $offset) { $offset = (int) $offset; if ($offset) { return "offset {$offset} rows"; } return ''; } /** * Compile the lock into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param bool|string $value * @return string */ protected function compileLock(Builder $query, $value) { return ''; } /** * Wrap a union subquery in parentheses. * * @param string $sql * @return string */ protected function wrapUnion($sql) { return 'select * from ('.$sql.') as '.$this->wrapTable('temp_table'); } /** * Compile an exists statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @return string */ public function compileExists(Builder $query) { $existsQuery = clone $query; $existsQuery->columns = []; return $this->compileSelect($existsQuery->selectRaw('1 [exists]')->limit(1)); } /** * Compile an update statement with joins into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param string $table * @param string $columns * @param string $where * @return string */ protected function compileUpdateWithJoins(Builder $query, $table, $columns, $where) { $alias = last(explode(' as ', $table)); $joins = $this->compileJoins($query, $query->joins); return "update {$alias} set {$columns} from {$table} {$joins} {$where}"; } /** * Compile an "upsert" statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param array $uniqueBy * @param array $update * @return string */ public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) { $columns = $this->columnize(array_keys(reset($values))); $sql = 'merge '.$this->wrapTable($query->from).' '; $parameters = collect($values)->map(function ($record) { return '('.$this->parameterize($record).')'; })->implode(', '); $sql .= 'using (values '.$parameters.') '.$this->wrapTable('laravel_source').' ('.$columns.') '; $on = collect($uniqueBy)->map(function ($column) use ($query) { return $this->wrap('laravel_source.'.$column).' = '.$this->wrap($query->from.'.'.$column); })->implode(' and '); $sql .= 'on '.$on.' '; if ($update) { $update = collect($update)->map(function ($value, $key) { return is_numeric($key) ? $this->wrap($value).' = '.$this->wrap('laravel_source.'.$value) : $this->wrap($key).' = '.$this->parameter($value); })->implode(', '); $sql .= 'when matched then update set '.$update.' '; } $sql .= 'when not matched then insert ('.$columns.') values ('.$columns.');'; return $sql; } /** * Prepare the bindings for an update statement. * * @param array $bindings * @param array $values * @return array */ public function prepareBindingsForUpdate(array $bindings, array $values) { $cleanBindings = Arr::except($bindings, 'select'); return array_values( array_merge($values, Arr::flatten($cleanBindings)) ); } /** * Compile the SQL statement to define a savepoint. * * @param string $name * @return string */ public function compileSavepoint($name) { return 'SAVE TRANSACTION '.$name; } /** * Compile the SQL statement to execute a savepoint rollback. * * @param string $name * @return string */ public function compileSavepointRollBack($name) { return 'ROLLBACK TRANSACTION '.$name; } /** * Get the format for database stored dates. * * @return string */ public function getDateFormat() { return 'Y-m-d H:i:s.v'; } /** * Wrap a single string in keyword identifiers. * * @param string $value * @return string */ protected function wrapValue($value) { return $value === '*' ? $value : '['.str_replace(']', ']]', $value).']'; } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_value('.$field.$path.')'; } /** * Wrap the given JSON boolean value. * * @param string $value * @return string */ protected function wrapJsonBooleanValue($value) { return "'".$value."'"; } /** * Wrap a table in keyword identifiers. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table * @return string */ public function wrapTable($table) { if (! $this->isExpression($table)) { return $this->wrapTableValuedFunction(parent::wrapTable($table)); } return $this->getValue($table); } /** * Wrap a table in keyword identifiers. * * @param string $table * @return string */ protected function wrapTableValuedFunction($table) { if (preg_match('/^(.+?)(\(.*?\))]$/', $table, $matches) === 1) { $table = $matches[1].']'.$matches[2]; } return $table; } } Query/IndexHint.php000075500000000777147536122140010277 0ustar00type = $type; $this->index = $index; } } Query/JoinClause.php000075500000007224147536122140010433 0ustar00type = $type; $this->table = $table; $this->parentClass = get_class($parentQuery); $this->parentGrammar = $parentQuery->getGrammar(); $this->parentProcessor = $parentQuery->getProcessor(); $this->parentConnection = $parentQuery->getConnection(); parent::__construct( $this->parentConnection, $this->parentGrammar, $this->parentProcessor ); } /** * Add an "on" clause to the join. * * On clauses can be chained, e.g. * * $join->on('contacts.user_id', '=', 'users.id') * ->on('contacts.info_id', '=', 'info.id') * * will produce the following SQL: * * on `contacts`.`user_id` = `users`.`id` and `contacts`.`info_id` = `info`.`id` * * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @param string $boolean * @return $this * * @throws \InvalidArgumentException */ public function on($first, $operator = null, $second = null, $boolean = 'and') { if ($first instanceof Closure) { return $this->whereNested($first, $boolean); } return $this->whereColumn($first, $operator, $second, $boolean); } /** * Add an "or on" clause to the join. * * @param \Closure|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return \Illuminate\Database\Query\JoinClause */ public function orOn($first, $operator = null, $second = null) { return $this->on($first, $operator, $second, 'or'); } /** * Get a new instance of the join clause builder. * * @return \Illuminate\Database\Query\JoinClause */ public function newQuery() { return new static($this->newParentQuery(), $this->type, $this->table); } /** * Create a new query instance for sub-query. * * @return \Illuminate\Database\Query\Builder */ protected function forSubQuery() { return $this->newParentQuery()->newQuery(); } /** * Create a new parent query instance. * * @return \Illuminate\Database\Query\Builder */ protected function newParentQuery() { $class = $this->parentClass; return new $class($this->parentConnection, $this->parentGrammar, $this->parentProcessor); } } Query/Processors/MySqlProcessor.php000064400000000633147536122140013500 0ustar00column_name; }, $results); } } Query/Processors/PostgresProcessor.php000075500000002157147536122140014247 0ustar00getConnection(); $connection->recordsHaveBeenModified(); $result = $connection->selectFromWriteConnection($sql, $values)[0]; $sequence = $sequence ?: 'id'; $id = is_object($result) ? $result->{$sequence} : $result[$sequence]; return is_numeric($id) ? (int) $id : $id; } /** * Process the results of a column listing query. * * @param array $results * @return array */ public function processColumnListing($results) { return array_map(function ($result) { return ((object) $result)->column_name; }, $results); } } Query/Processors/Processor.php000075500000002155147536122140012516 0ustar00getConnection()->insert($sql, $values); $id = $query->getConnection()->getPdo()->lastInsertId($sequence); return is_numeric($id) ? (int) $id : $id; } /** * Process the results of a column listing query. * * @param array $results * @return array */ public function processColumnListing($results) { return $results; } } Query/Processors/SQLiteProcessor.php000064400000000625147536122140013575 0ustar00name; }, $results); } } Query/Processors/SqlServerProcessor.php000075500000003435147536122140014367 0ustar00getConnection(); $connection->insert($sql, $values); if ($connection->getConfig('odbc') === true) { $id = $this->processInsertGetIdForOdbc($connection); } else { $id = $connection->getPdo()->lastInsertId(); } return is_numeric($id) ? (int) $id : $id; } /** * Process an "insert get ID" query for ODBC. * * @param \Illuminate\Database\Connection $connection * @return int * * @throws \Exception */ protected function processInsertGetIdForOdbc(Connection $connection) { $result = $connection->selectFromWriteConnection( 'SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS int) AS insertid' ); if (! $result) { throw new Exception('Unable to retrieve lastInsertID for ODBC.'); } $row = $result[0]; return is_object($row) ? $row->insertid : $row['insertid']; } /** * Process the results of a column listing query. * * @param array $results * @return array */ public function processColumnListing($results) { return array_map(function ($result) { return ((object) $result)->name; }, $results); } } QueryException.php000064400000004124147536122140010247 0ustar00connectionName = $connectionName; $this->sql = $sql; $this->bindings = $bindings; $this->code = $previous->getCode(); $this->message = $this->formatMessage($connectionName, $sql, $bindings, $previous); if ($previous instanceof PDOException) { $this->errorInfo = $previous->errorInfo; } } /** * Format the SQL error message. * * @param string $connectionName * @param string $sql * @param array $bindings * @param \Throwable $previous * @return string */ protected function formatMessage($connectionName, $sql, $bindings, Throwable $previous) { return $previous->getMessage().' (Connection: '.$connectionName.', SQL: '.Str::replaceArray('?', $bindings, $sql).')'; } /** * Get the connection name for the query. * * @return string */ public function getConnectionName() { return $this->connectionName; } /** * Get the SQL for the query. * * @return string */ public function getSql() { return $this->sql; } /** * Get the bindings for the query. * * @return array */ public function getBindings() { return $this->bindings; } } README.md000075500000004240147536122140006033 0ustar00## Illuminate Database The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style ORM, and schema builder. It currently supports MySQL, Postgres, SQL Server, and SQLite. It also serves as the database layer of the Laravel PHP framework. ### Usage Instructions First, create a new "Capsule" manager instance. Capsule aims to make configuring the library for usage outside of the Laravel framework as easy as possible. ```PHP use Illuminate\Database\Capsule\Manager as Capsule; $capsule = new Capsule; $capsule->addConnection([ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'database', 'username' => 'root', 'password' => 'password', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ]); // Set the event dispatcher used by Eloquent models... (optional) use Illuminate\Events\Dispatcher; use Illuminate\Container\Container; $capsule->setEventDispatcher(new Dispatcher(new Container)); // Make this Capsule instance available globally via static methods... (optional) $capsule->setAsGlobal(); // Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher()) $capsule->bootEloquent(); ``` > `composer require "illuminate/events"` required when you need to use observers with Eloquent. Once the Capsule instance has been registered. You may use it like so: **Using The Query Builder** ```PHP $users = Capsule::table('users')->where('votes', '>', 100)->get(); ``` Other core methods may be accessed directly from the Capsule in the same manner as from the DB facade: ```PHP $results = Capsule::select('select * from users where id = ?', [1]); ``` **Using The Schema Builder** ```PHP Capsule::schema()->create('users', function ($table) { $table->increments('id'); $table->string('email')->unique(); $table->timestamps(); }); ``` **Using The Eloquent ORM** ```PHP class User extends Illuminate\Database\Eloquent\Model {} $users = User::where('votes', '>', 1)->get(); ``` For further documentation on using the various database facilities this library provides, consult the [Laravel framework documentation](https://laravel.com/docs). RecordsNotFoundException.php000075500000000201147536122140012213 0ustar00getForeignKeyConstraintsConfigurationValue(); if ($enableForeignKeyConstraints === null) { return; } $enableForeignKeyConstraints ? $this->getSchemaBuilder()->enableForeignKeyConstraints() : $this->getSchemaBuilder()->disableForeignKeyConstraints(); } /** * Escape a binary value for safe SQL embedding. * * @param string $value * @return string */ protected function escapeBinary($value) { $hex = bin2hex($value); return "x'{$hex}'"; } /** * Determine if the given database exception was caused by a unique constraint violation. * * @param \Exception $exception * @return bool */ protected function isUniqueConstraintError(Exception $exception) { return boolval(preg_match('#(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)#i', $exception->getMessage())); } /** * Get the default query grammar instance. * * @return \Illuminate\Database\Query\Grammars\SQLiteGrammar */ protected function getDefaultQueryGrammar() { ($grammar = new QueryGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * Get a schema builder instance for the connection. * * @return \Illuminate\Database\Schema\SQLiteBuilder */ public function getSchemaBuilder() { if (is_null($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } return new SQLiteBuilder($this); } /** * Get the default schema grammar instance. * * @return \Illuminate\Database\Schema\Grammars\SQLiteGrammar */ protected function getDefaultSchemaGrammar() { ($grammar = new SchemaGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * Get the schema state for the connection. * * @param \Illuminate\Filesystem\Filesystem|null $files * @param callable|null $processFactory * * @throws \RuntimeException */ public function getSchemaState(Filesystem $files = null, callable $processFactory = null) { return new SqliteSchemaState($this, $files, $processFactory); } /** * Get the default post processor instance. * * @return \Illuminate\Database\Query\Processors\SQLiteProcessor */ protected function getDefaultPostProcessor() { return new SQLiteProcessor; } /** * Get the Doctrine DBAL driver. * * @return \Illuminate\Database\PDO\SQLiteDriver */ protected function getDoctrineDriver() { return new SQLiteDriver; } /** * Get the database connection foreign key constraints configuration option. * * @return bool|null */ protected function getForeignKeyConstraintsConfigurationValue() { return $this->getConfig('foreign_key_constraints'); } } SQLiteDatabaseDoesNotExistException.php000064400000001065147536122140014242 0ustar00path = $path; } } Schema/Blueprint.php000075500000142663147536122140010445 0ustar00table = $table; $this->prefix = $prefix; if (! is_null($callback)) { $callback($this); } } /** * Execute the blueprint against the database. * * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ public function build(Connection $connection, Grammar $grammar) { foreach ($this->toSql($connection, $grammar) as $statement) { $connection->statement($statement); } } /** * Get the raw SQL statements for the blueprint. * * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return array */ public function toSql(Connection $connection, Grammar $grammar) { $this->addImpliedCommands($connection, $grammar); $statements = []; // Each type of command has a corresponding compiler function on the schema // grammar which is used to build the necessary SQL statements to build // the blueprint element, so we'll just call that compilers function. $this->ensureCommandsAreValid($connection); foreach ($this->commands as $command) { $method = 'compile'.ucfirst($command->name); if (method_exists($grammar, $method) || $grammar::hasMacro($method)) { if (! is_null($sql = $grammar->$method($this, $command, $connection))) { $statements = array_merge($statements, (array) $sql); } } } return $statements; } /** * Ensure the commands on the blueprint are valid for the connection type. * * @param \Illuminate\Database\Connection $connection * @return void * * @throws \BadMethodCallException */ protected function ensureCommandsAreValid(Connection $connection) { if ($connection instanceof SQLiteConnection) { if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1 && ! $connection->usingNativeSchemaOperations()) { throw new BadMethodCallException( "SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification." ); } if ($this->commandsNamed(['dropForeign'])->count() > 0) { throw new BadMethodCallException( "SQLite doesn't support dropping foreign keys (you would need to re-create the table)." ); } } } /** * Get all of the commands matching the given names. * * @param array $names * @return \Illuminate\Support\Collection */ protected function commandsNamed(array $names) { return collect($this->commands)->filter(function ($command) use ($names) { return in_array($command->name, $names); }); } /** * Add the commands that are implied by the blueprint's state. * * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ protected function addImpliedCommands(Connection $connection, Grammar $grammar) { if (count($this->getAddedColumns()) > 0 && ! $this->creating()) { array_unshift($this->commands, $this->createCommand('add')); } if (count($this->getChangedColumns()) > 0 && ! $this->creating()) { array_unshift($this->commands, $this->createCommand('change')); } $this->addFluentIndexes(); $this->addFluentCommands($connection, $grammar); } /** * Add the index commands fluently specified on columns. * * @return void */ protected function addFluentIndexes() { foreach ($this->columns as $column) { foreach (['primary', 'unique', 'index', 'fulltext', 'fullText', 'spatialIndex'] as $index) { // If the index has been specified on the given column, but is simply equal // to "true" (boolean), no name has been specified for this index so the // index method can be called without a name and it will generate one. if ($column->{$index} === true) { $this->{$index}($column->name); $column->{$index} = null; continue 2; } // If the index has been specified on the given column, but it equals false // and the column is supposed to be changed, we will call the drop index // method with an array of column to drop it by its conventional name. elseif ($column->{$index} === false && $column->change) { $this->{'drop'.ucfirst($index)}([$column->name]); $column->{$index} = null; continue 2; } // If the index has been specified on the given column, and it has a string // value, we'll go ahead and call the index method and pass the name for // the index since the developer specified the explicit name for this. elseif (isset($column->{$index})) { $this->{$index}($column->name, $column->{$index}); $column->{$index} = null; continue 2; } } } } /** * Add the fluent commands specified on any columns. * * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return void */ public function addFluentCommands(Connection $connection, Grammar $grammar) { foreach ($this->columns as $column) { if ($column->change && ! $connection->usingNativeSchemaOperations()) { continue; } foreach ($grammar->getFluentCommands() as $commandName) { $this->addCommand($commandName, compact('column')); } } } /** * Determine if the blueprint has a create command. * * @return bool */ public function creating() { return collect($this->commands)->contains(function ($command) { return $command->name === 'create'; }); } /** * Indicate that the table needs to be created. * * @return \Illuminate\Support\Fluent */ public function create() { return $this->addCommand('create'); } /** * Indicate that the table needs to be temporary. * * @return void */ public function temporary() { $this->temporary = true; } /** * Indicate that the table should be dropped. * * @return \Illuminate\Support\Fluent */ public function drop() { return $this->addCommand('drop'); } /** * Indicate that the table should be dropped if it exists. * * @return \Illuminate\Support\Fluent */ public function dropIfExists() { return $this->addCommand('dropIfExists'); } /** * Indicate that the given columns should be dropped. * * @param array|mixed $columns * @return \Illuminate\Support\Fluent */ public function dropColumn($columns) { $columns = is_array($columns) ? $columns : func_get_args(); return $this->addCommand('dropColumn', compact('columns')); } /** * Indicate that the given columns should be renamed. * * @param string $from * @param string $to * @return \Illuminate\Support\Fluent */ public function renameColumn($from, $to) { return $this->addCommand('renameColumn', compact('from', 'to')); } /** * Indicate that the given primary key should be dropped. * * @param string|array|null $index * @return \Illuminate\Support\Fluent */ public function dropPrimary($index = null) { return $this->dropIndexCommand('dropPrimary', 'primary', $index); } /** * Indicate that the given unique key should be dropped. * * @param string|array $index * @return \Illuminate\Support\Fluent */ public function dropUnique($index) { return $this->dropIndexCommand('dropUnique', 'unique', $index); } /** * Indicate that the given index should be dropped. * * @param string|array $index * @return \Illuminate\Support\Fluent */ public function dropIndex($index) { return $this->dropIndexCommand('dropIndex', 'index', $index); } /** * Indicate that the given fulltext index should be dropped. * * @param string|array $index * @return \Illuminate\Support\Fluent */ public function dropFullText($index) { return $this->dropIndexCommand('dropFullText', 'fulltext', $index); } /** * Indicate that the given spatial index should be dropped. * * @param string|array $index * @return \Illuminate\Support\Fluent */ public function dropSpatialIndex($index) { return $this->dropIndexCommand('dropSpatialIndex', 'spatialIndex', $index); } /** * Indicate that the given foreign key should be dropped. * * @param string|array $index * @return \Illuminate\Support\Fluent */ public function dropForeign($index) { return $this->dropIndexCommand('dropForeign', 'foreign', $index); } /** * Indicate that the given column and foreign key should be dropped. * * @param string $column * @return \Illuminate\Support\Fluent */ public function dropConstrainedForeignId($column) { $this->dropForeign([$column]); return $this->dropColumn($column); } /** * Indicate that the given foreign key should be dropped. * * @param \Illuminate\Database\Eloquent\Model|string $model * @param string|null $column * @return \Illuminate\Support\Fluent */ public function dropForeignIdFor($model, $column = null) { if (is_string($model)) { $model = new $model; } return $this->dropForeign([$column ?: $model->getForeignKey()]); } /** * Indicate that the given foreign key should be dropped. * * @param \Illuminate\Database\Eloquent\Model|string $model * @param string|null $column * @return \Illuminate\Support\Fluent */ public function dropConstrainedForeignIdFor($model, $column = null) { if (is_string($model)) { $model = new $model; } return $this->dropConstrainedForeignId($column ?: $model->getForeignKey()); } /** * Indicate that the given indexes should be renamed. * * @param string $from * @param string $to * @return \Illuminate\Support\Fluent */ public function renameIndex($from, $to) { return $this->addCommand('renameIndex', compact('from', 'to')); } /** * Indicate that the timestamp columns should be dropped. * * @return void */ public function dropTimestamps() { $this->dropColumn('created_at', 'updated_at'); } /** * Indicate that the timestamp columns should be dropped. * * @return void */ public function dropTimestampsTz() { $this->dropTimestamps(); } /** * Indicate that the soft delete column should be dropped. * * @param string $column * @return void */ public function dropSoftDeletes($column = 'deleted_at') { $this->dropColumn($column); } /** * Indicate that the soft delete column should be dropped. * * @param string $column * @return void */ public function dropSoftDeletesTz($column = 'deleted_at') { $this->dropSoftDeletes($column); } /** * Indicate that the remember token column should be dropped. * * @return void */ public function dropRememberToken() { $this->dropColumn('remember_token'); } /** * Indicate that the polymorphic columns should be dropped. * * @param string $name * @param string|null $indexName * @return void */ public function dropMorphs($name, $indexName = null) { $this->dropIndex($indexName ?: $this->createIndexName('index', ["{$name}_type", "{$name}_id"])); $this->dropColumn("{$name}_type", "{$name}_id"); } /** * Rename the table to a given name. * * @param string $to * @return \Illuminate\Support\Fluent */ public function rename($to) { return $this->addCommand('rename', compact('to')); } /** * Specify the primary key(s) for the table. * * @param string|array $columns * @param string|null $name * @param string|null $algorithm * @return \Illuminate\Database\Schema\IndexDefinition */ public function primary($columns, $name = null, $algorithm = null) { return $this->indexCommand('primary', $columns, $name, $algorithm); } /** * Specify a unique index for the table. * * @param string|array $columns * @param string|null $name * @param string|null $algorithm * @return \Illuminate\Database\Schema\IndexDefinition */ public function unique($columns, $name = null, $algorithm = null) { return $this->indexCommand('unique', $columns, $name, $algorithm); } /** * Specify an index for the table. * * @param string|array $columns * @param string|null $name * @param string|null $algorithm * @return \Illuminate\Database\Schema\IndexDefinition */ public function index($columns, $name = null, $algorithm = null) { return $this->indexCommand('index', $columns, $name, $algorithm); } /** * Specify an fulltext for the table. * * @param string|array $columns * @param string|null $name * @param string|null $algorithm * @return \Illuminate\Database\Schema\IndexDefinition */ public function fullText($columns, $name = null, $algorithm = null) { return $this->indexCommand('fulltext', $columns, $name, $algorithm); } /** * Specify a spatial index for the table. * * @param string|array $columns * @param string|null $name * @return \Illuminate\Database\Schema\IndexDefinition */ public function spatialIndex($columns, $name = null) { return $this->indexCommand('spatialIndex', $columns, $name); } /** * Specify a raw index for the table. * * @param string $expression * @param string $name * @return \Illuminate\Database\Schema\IndexDefinition */ public function rawIndex($expression, $name) { return $this->index([new Expression($expression)], $name); } /** * Specify a foreign key for the table. * * @param string|array $columns * @param string|null $name * @return \Illuminate\Database\Schema\ForeignKeyDefinition */ public function foreign($columns, $name = null) { $command = new ForeignKeyDefinition( $this->indexCommand('foreign', $columns, $name)->getAttributes() ); $this->commands[count($this->commands) - 1] = $command; return $command; } /** * Create a new auto-incrementing big integer (8-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function id($column = 'id') { return $this->bigIncrements($column); } /** * Create a new auto-incrementing integer (4-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function increments($column) { return $this->unsignedInteger($column, true); } /** * Create a new auto-incrementing integer (4-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function integerIncrements($column) { return $this->unsignedInteger($column, true); } /** * Create a new auto-incrementing tiny integer (1-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function tinyIncrements($column) { return $this->unsignedTinyInteger($column, true); } /** * Create a new auto-incrementing small integer (2-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function smallIncrements($column) { return $this->unsignedSmallInteger($column, true); } /** * Create a new auto-incrementing medium integer (3-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function mediumIncrements($column) { return $this->unsignedMediumInteger($column, true); } /** * Create a new auto-incrementing big integer (8-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function bigIncrements($column) { return $this->unsignedBigInteger($column, true); } /** * Create a new char column on the table. * * @param string $column * @param int|null $length * @return \Illuminate\Database\Schema\ColumnDefinition */ public function char($column, $length = null) { $length = ! is_null($length) ? $length : Builder::$defaultStringLength; return $this->addColumn('char', $column, compact('length')); } /** * Create a new string column on the table. * * @param string $column * @param int|null $length * @return \Illuminate\Database\Schema\ColumnDefinition */ public function string($column, $length = null) { $length = $length ?: Builder::$defaultStringLength; return $this->addColumn('string', $column, compact('length')); } /** * Create a new tiny text column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function tinyText($column) { return $this->addColumn('tinyText', $column); } /** * Create a new text column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function text($column) { return $this->addColumn('text', $column); } /** * Create a new medium text column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function mediumText($column) { return $this->addColumn('mediumText', $column); } /** * Create a new long text column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function longText($column) { return $this->addColumn('longText', $column); } /** * Create a new integer (4-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function integer($column, $autoIncrement = false, $unsigned = false) { return $this->addColumn('integer', $column, compact('autoIncrement', 'unsigned')); } /** * Create a new tiny integer (1-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function tinyInteger($column, $autoIncrement = false, $unsigned = false) { return $this->addColumn('tinyInteger', $column, compact('autoIncrement', 'unsigned')); } /** * Create a new small integer (2-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function smallInteger($column, $autoIncrement = false, $unsigned = false) { return $this->addColumn('smallInteger', $column, compact('autoIncrement', 'unsigned')); } /** * Create a new medium integer (3-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function mediumInteger($column, $autoIncrement = false, $unsigned = false) { return $this->addColumn('mediumInteger', $column, compact('autoIncrement', 'unsigned')); } /** * Create a new big integer (8-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function bigInteger($column, $autoIncrement = false, $unsigned = false) { return $this->addColumn('bigInteger', $column, compact('autoIncrement', 'unsigned')); } /** * Create a new unsigned integer (4-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedInteger($column, $autoIncrement = false) { return $this->integer($column, $autoIncrement, true); } /** * Create a new unsigned tiny integer (1-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedTinyInteger($column, $autoIncrement = false) { return $this->tinyInteger($column, $autoIncrement, true); } /** * Create a new unsigned small integer (2-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedSmallInteger($column, $autoIncrement = false) { return $this->smallInteger($column, $autoIncrement, true); } /** * Create a new unsigned medium integer (3-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedMediumInteger($column, $autoIncrement = false) { return $this->mediumInteger($column, $autoIncrement, true); } /** * Create a new unsigned big integer (8-byte) column on the table. * * @param string $column * @param bool $autoIncrement * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedBigInteger($column, $autoIncrement = false) { return $this->bigInteger($column, $autoIncrement, true); } /** * Create a new unsigned big integer (8-byte) column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition */ public function foreignId($column) { return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ 'type' => 'bigInteger', 'name' => $column, 'autoIncrement' => false, 'unsigned' => true, ])); } /** * Create a foreign ID column for the given model. * * @param \Illuminate\Database\Eloquent\Model|string $model * @param string|null $column * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition */ public function foreignIdFor($model, $column = null) { if (is_string($model)) { $model = new $model; } $column = $column ?: $model->getForeignKey(); if ($model->getKeyType() === 'int' && $model->getIncrementing()) { return $this->foreignId($column); } $modelTraits = class_uses_recursive($model); if (in_array(HasUlids::class, $modelTraits, true)) { return $this->foreignUlid($column); } return $this->foreignUuid($column); } /** * Create a new float column on the table. * * @param string $column * @param int $total * @param int $places * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function float($column, $total = 8, $places = 2, $unsigned = false) { return $this->addColumn('float', $column, compact('total', 'places', 'unsigned')); } /** * Create a new double column on the table. * * @param string $column * @param int|null $total * @param int|null $places * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function double($column, $total = null, $places = null, $unsigned = false) { return $this->addColumn('double', $column, compact('total', 'places', 'unsigned')); } /** * Create a new decimal column on the table. * * @param string $column * @param int $total * @param int $places * @param bool $unsigned * @return \Illuminate\Database\Schema\ColumnDefinition */ public function decimal($column, $total = 8, $places = 2, $unsigned = false) { return $this->addColumn('decimal', $column, compact('total', 'places', 'unsigned')); } /** * Create a new unsigned float column on the table. * * @param string $column * @param int $total * @param int $places * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedFloat($column, $total = 8, $places = 2) { return $this->float($column, $total, $places, true); } /** * Create a new unsigned double column on the table. * * @param string $column * @param int $total * @param int $places * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedDouble($column, $total = null, $places = null) { return $this->double($column, $total, $places, true); } /** * Create a new unsigned decimal column on the table. * * @param string $column * @param int $total * @param int $places * @return \Illuminate\Database\Schema\ColumnDefinition */ public function unsignedDecimal($column, $total = 8, $places = 2) { return $this->decimal($column, $total, $places, true); } /** * Create a new boolean column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function boolean($column) { return $this->addColumn('boolean', $column); } /** * Create a new enum column on the table. * * @param string $column * @param array $allowed * @return \Illuminate\Database\Schema\ColumnDefinition */ public function enum($column, array $allowed) { return $this->addColumn('enum', $column, compact('allowed')); } /** * Create a new set column on the table. * * @param string $column * @param array $allowed * @return \Illuminate\Database\Schema\ColumnDefinition */ public function set($column, array $allowed) { return $this->addColumn('set', $column, compact('allowed')); } /** * Create a new json column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function json($column) { return $this->addColumn('json', $column); } /** * Create a new jsonb column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function jsonb($column) { return $this->addColumn('jsonb', $column); } /** * Create a new date column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function date($column) { return $this->addColumn('date', $column); } /** * Create a new date-time column on the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function dateTime($column, $precision = 0) { return $this->addColumn('dateTime', $column, compact('precision')); } /** * Create a new date-time column (with time zone) on the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function dateTimeTz($column, $precision = 0) { return $this->addColumn('dateTimeTz', $column, compact('precision')); } /** * Create a new time column on the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function time($column, $precision = 0) { return $this->addColumn('time', $column, compact('precision')); } /** * Create a new time column (with time zone) on the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function timeTz($column, $precision = 0) { return $this->addColumn('timeTz', $column, compact('precision')); } /** * Create a new timestamp column on the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function timestamp($column, $precision = 0) { return $this->addColumn('timestamp', $column, compact('precision')); } /** * Create a new timestamp (with time zone) column on the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function timestampTz($column, $precision = 0) { return $this->addColumn('timestampTz', $column, compact('precision')); } /** * Add nullable creation and update timestamps to the table. * * @param int|null $precision * @return void */ public function timestamps($precision = 0) { $this->timestamp('created_at', $precision)->nullable(); $this->timestamp('updated_at', $precision)->nullable(); } /** * Add nullable creation and update timestamps to the table. * * Alias for self::timestamps(). * * @param int|null $precision * @return void */ public function nullableTimestamps($precision = 0) { $this->timestamps($precision); } /** * Add creation and update timestampTz columns to the table. * * @param int|null $precision * @return void */ public function timestampsTz($precision = 0) { $this->timestampTz('created_at', $precision)->nullable(); $this->timestampTz('updated_at', $precision)->nullable(); } /** * Add creation and update datetime columns to the table. * * @param int|null $precision * @return void */ public function datetimes($precision = 0) { $this->datetime('created_at', $precision)->nullable(); $this->datetime('updated_at', $precision)->nullable(); } /** * Add a "deleted at" timestamp for the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function softDeletes($column = 'deleted_at', $precision = 0) { return $this->timestamp($column, $precision)->nullable(); } /** * Add a "deleted at" timestampTz for the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function softDeletesTz($column = 'deleted_at', $precision = 0) { return $this->timestampTz($column, $precision)->nullable(); } /** * Add a "deleted at" datetime column to the table. * * @param string $column * @param int|null $precision * @return \Illuminate\Database\Schema\ColumnDefinition */ public function softDeletesDatetime($column = 'deleted_at', $precision = 0) { return $this->datetime($column, $precision)->nullable(); } /** * Create a new year column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function year($column) { return $this->addColumn('year', $column); } /** * Create a new binary column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function binary($column) { return $this->addColumn('binary', $column); } /** * Create a new UUID column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function uuid($column = 'uuid') { return $this->addColumn('uuid', $column); } /** * Create a new UUID column on the table with a foreign key constraint. * * @param string $column * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition */ public function foreignUuid($column) { return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ 'type' => 'uuid', 'name' => $column, ])); } /** * Create a new ULID column on the table. * * @param string $column * @param int|null $length * @return \Illuminate\Database\Schema\ColumnDefinition */ public function ulid($column = 'ulid', $length = 26) { return $this->char($column, $length); } /** * Create a new ULID column on the table with a foreign key constraint. * * @param string $column * @param int|null $length * @return \Illuminate\Database\Schema\ForeignIdColumnDefinition */ public function foreignUlid($column, $length = 26) { return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [ 'type' => 'char', 'name' => $column, 'length' => $length, ])); } /** * Create a new IP address column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function ipAddress($column = 'ip_address') { return $this->addColumn('ipAddress', $column); } /** * Create a new MAC address column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function macAddress($column = 'mac_address') { return $this->addColumn('macAddress', $column); } /** * Create a new geometry column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function geometry($column) { return $this->addColumn('geometry', $column); } /** * Create a new point column on the table. * * @param string $column * @param int|null $srid * @return \Illuminate\Database\Schema\ColumnDefinition */ public function point($column, $srid = null) { return $this->addColumn('point', $column, compact('srid')); } /** * Create a new linestring column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function lineString($column) { return $this->addColumn('linestring', $column); } /** * Create a new polygon column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function polygon($column) { return $this->addColumn('polygon', $column); } /** * Create a new geometrycollection column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function geometryCollection($column) { return $this->addColumn('geometrycollection', $column); } /** * Create a new multipoint column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function multiPoint($column) { return $this->addColumn('multipoint', $column); } /** * Create a new multilinestring column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function multiLineString($column) { return $this->addColumn('multilinestring', $column); } /** * Create a new multipolygon column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function multiPolygon($column) { return $this->addColumn('multipolygon', $column); } /** * Create a new multipolygon column on the table. * * @param string $column * @return \Illuminate\Database\Schema\ColumnDefinition */ public function multiPolygonZ($column) { return $this->addColumn('multipolygonz', $column); } /** * Create a new generated, computed column on the table. * * @param string $column * @param string $expression * @return \Illuminate\Database\Schema\ColumnDefinition */ public function computed($column, $expression) { return $this->addColumn('computed', $column, compact('expression')); } /** * Add the proper columns for a polymorphic table. * * @param string $name * @param string|null $indexName * @return void */ public function morphs($name, $indexName = null) { if (Builder::$defaultMorphKeyType === 'uuid') { $this->uuidMorphs($name, $indexName); } elseif (Builder::$defaultMorphKeyType === 'ulid') { $this->ulidMorphs($name, $indexName); } else { $this->numericMorphs($name, $indexName); } } /** * Add nullable columns for a polymorphic table. * * @param string $name * @param string|null $indexName * @return void */ public function nullableMorphs($name, $indexName = null) { if (Builder::$defaultMorphKeyType === 'uuid') { $this->nullableUuidMorphs($name, $indexName); } elseif (Builder::$defaultMorphKeyType === 'ulid') { $this->nullableUlidMorphs($name, $indexName); } else { $this->nullableNumericMorphs($name, $indexName); } } /** * Add the proper columns for a polymorphic table using numeric IDs (incremental). * * @param string $name * @param string|null $indexName * @return void */ public function numericMorphs($name, $indexName = null) { $this->string("{$name}_type"); $this->unsignedBigInteger("{$name}_id"); $this->index(["{$name}_type", "{$name}_id"], $indexName); } /** * Add nullable columns for a polymorphic table using numeric IDs (incremental). * * @param string $name * @param string|null $indexName * @return void */ public function nullableNumericMorphs($name, $indexName = null) { $this->string("{$name}_type")->nullable(); $this->unsignedBigInteger("{$name}_id")->nullable(); $this->index(["{$name}_type", "{$name}_id"], $indexName); } /** * Add the proper columns for a polymorphic table using UUIDs. * * @param string $name * @param string|null $indexName * @return void */ public function uuidMorphs($name, $indexName = null) { $this->string("{$name}_type"); $this->uuid("{$name}_id"); $this->index(["{$name}_type", "{$name}_id"], $indexName); } /** * Add nullable columns for a polymorphic table using UUIDs. * * @param string $name * @param string|null $indexName * @return void */ public function nullableUuidMorphs($name, $indexName = null) { $this->string("{$name}_type")->nullable(); $this->uuid("{$name}_id")->nullable(); $this->index(["{$name}_type", "{$name}_id"], $indexName); } /** * Add the proper columns for a polymorphic table using ULIDs. * * @param string $name * @param string|null $indexName * @return void */ public function ulidMorphs($name, $indexName = null) { $this->string("{$name}_type"); $this->ulid("{$name}_id"); $this->index(["{$name}_type", "{$name}_id"], $indexName); } /** * Add nullable columns for a polymorphic table using ULIDs. * * @param string $name * @param string|null $indexName * @return void */ public function nullableUlidMorphs($name, $indexName = null) { $this->string("{$name}_type")->nullable(); $this->ulid("{$name}_id")->nullable(); $this->index(["{$name}_type", "{$name}_id"], $indexName); } /** * Adds the `remember_token` column to the table. * * @return \Illuminate\Database\Schema\ColumnDefinition */ public function rememberToken() { return $this->string('remember_token', 100)->nullable(); } /** * Add a comment to the table. * * @param string $comment * @return \Illuminate\Support\Fluent */ public function comment($comment) { return $this->addCommand('tableComment', compact('comment')); } /** * Add a new index command to the blueprint. * * @param string $type * @param string|array $columns * @param string $index * @param string|null $algorithm * @return \Illuminate\Support\Fluent */ protected function indexCommand($type, $columns, $index, $algorithm = null) { $columns = (array) $columns; // If no name was specified for this index, we will create one using a basic // convention of the table name, followed by the columns, followed by an // index type, such as primary or index, which makes the index unique. $index = $index ?: $this->createIndexName($type, $columns); return $this->addCommand( $type, compact('index', 'columns', 'algorithm') ); } /** * Create a new drop index command on the blueprint. * * @param string $command * @param string $type * @param string|array $index * @return \Illuminate\Support\Fluent */ protected function dropIndexCommand($command, $type, $index) { $columns = []; // If the given "index" is actually an array of columns, the developer means // to drop an index merely by specifying the columns involved without the // conventional name, so we will build the index name from the columns. if (is_array($index)) { $index = $this->createIndexName($type, $columns = $index); } return $this->indexCommand($command, $columns, $index); } /** * Create a default index name for the table. * * @param string $type * @param array $columns * @return string */ protected function createIndexName($type, array $columns) { $index = strtolower($this->prefix.$this->table.'_'.implode('_', $columns).'_'.$type); return str_replace(['-', '.'], '_', $index); } /** * Add a new column to the blueprint. * * @param string $type * @param string $name * @param array $parameters * @return \Illuminate\Database\Schema\ColumnDefinition */ public function addColumn($type, $name, array $parameters = []) { return $this->addColumnDefinition(new ColumnDefinition( array_merge(compact('type', 'name'), $parameters) )); } /** * Add a new column definition to the blueprint. * * @param \Illuminate\Database\Schema\ColumnDefinition $definition * @return \Illuminate\Database\Schema\ColumnDefinition */ protected function addColumnDefinition($definition) { $this->columns[] = $definition; if ($this->after) { $definition->after($this->after); $this->after = $definition->name; } return $definition; } /** * Add the columns from the callback after the given column. * * @param string $column * @param \Closure $callback * @return void */ public function after($column, Closure $callback) { $this->after = $column; $callback($this); $this->after = null; } /** * Remove a column from the schema blueprint. * * @param string $name * @return $this */ public function removeColumn($name) { $this->columns = array_values(array_filter($this->columns, function ($c) use ($name) { return $c['name'] != $name; })); return $this; } /** * Add a new command to the blueprint. * * @param string $name * @param array $parameters * @return \Illuminate\Support\Fluent */ protected function addCommand($name, array $parameters = []) { $this->commands[] = $command = $this->createCommand($name, $parameters); return $command; } /** * Create a new Fluent command. * * @param string $name * @param array $parameters * @return \Illuminate\Support\Fluent */ protected function createCommand($name, array $parameters = []) { return new Fluent(array_merge(compact('name'), $parameters)); } /** * Get the table the blueprint describes. * * @return string */ public function getTable() { return $this->table; } /** * Get the table prefix. * * @return string */ public function getPrefix() { return $this->prefix; } /** * Get the columns on the blueprint. * * @return \Illuminate\Database\Schema\ColumnDefinition[] */ public function getColumns() { return $this->columns; } /** * Get the commands on the blueprint. * * @return \Illuminate\Support\Fluent[] */ public function getCommands() { return $this->commands; } /** * Get the columns on the blueprint that should be added. * * @return \Illuminate\Database\Schema\ColumnDefinition[] */ public function getAddedColumns() { return array_filter($this->columns, function ($column) { return ! $column->change; }); } /** * Get the columns on the blueprint that should be changed. * * @return \Illuminate\Database\Schema\ColumnDefinition[] */ public function getChangedColumns() { return array_filter($this->columns, function ($column) { return (bool) $column->change; }); } } Schema/Builder.php000075500000027556147536122140010072 0ustar00connection = $connection; $this->grammar = $connection->getSchemaGrammar(); } /** * Set the default string length for migrations. * * @param int $length * @return void */ public static function defaultStringLength($length) { static::$defaultStringLength = $length; } /** * Set the default morph key type for migrations. * * @param string $type * @return void * * @throws \InvalidArgumentException */ public static function defaultMorphKeyType(string $type) { if (! in_array($type, ['int', 'uuid', 'ulid'])) { throw new InvalidArgumentException("Morph key type must be 'int', 'uuid', or 'ulid'."); } static::$defaultMorphKeyType = $type; } /** * Set the default morph key type for migrations to UUIDs. * * @return void */ public static function morphUsingUuids() { return static::defaultMorphKeyType('uuid'); } /** * Set the default morph key type for migrations to ULIDs. * * @return void */ public static function morphUsingUlids() { return static::defaultMorphKeyType('ulid'); } /** * Attempt to use native schema operations for dropping, renaming, and modifying columns, even if Doctrine DBAL is installed. * * @param bool $value * @return void */ public static function useNativeSchemaOperationsIfPossible(bool $value = true) { static::$alwaysUsesNativeSchemaOperationsIfPossible = $value; } /** * Create a database in the schema. * * @param string $name * @return bool * * @throws \LogicException */ public function createDatabase($name) { throw new LogicException('This database driver does not support creating databases.'); } /** * Drop a database from the schema if the database exists. * * @param string $name * @return bool * * @throws \LogicException */ public function dropDatabaseIfExists($name) { throw new LogicException('This database driver does not support dropping databases.'); } /** * Determine if the given table exists. * * @param string $table * @return bool */ public function hasTable($table) { $table = $this->connection->getTablePrefix().$table; return count($this->connection->selectFromWriteConnection( $this->grammar->compileTableExists(), [$table] )) > 0; } /** * Determine if the given table has a given column. * * @param string $table * @param string $column * @return bool */ public function hasColumn($table, $column) { return in_array( strtolower($column), array_map('strtolower', $this->getColumnListing($table)) ); } /** * Determine if the given table has given columns. * * @param string $table * @param array $columns * @return bool */ public function hasColumns($table, array $columns) { $tableColumns = array_map('strtolower', $this->getColumnListing($table)); foreach ($columns as $column) { if (! in_array(strtolower($column), $tableColumns)) { return false; } } return true; } /** * Execute a table builder callback if the given table has a given column. * * @param string $table * @param string $column * @param \Closure $callback * @return void */ public function whenTableHasColumn(string $table, string $column, Closure $callback) { if ($this->hasColumn($table, $column)) { $this->table($table, fn (Blueprint $table) => $callback($table)); } } /** * Execute a table builder callback if the given table doesn't have a given column. * * @param string $table * @param string $column * @param \Closure $callback * @return void */ public function whenTableDoesntHaveColumn(string $table, string $column, Closure $callback) { if (! $this->hasColumn($table, $column)) { $this->table($table, fn (Blueprint $table) => $callback($table)); } } /** * Get the data type for the given column name. * * @param string $table * @param string $column * @return string */ public function getColumnType($table, $column) { $table = $this->connection->getTablePrefix().$table; return $this->connection->getDoctrineColumn($table, $column)->getType()->getName(); } /** * Get the column listing for a given table. * * @param string $table * @return array */ public function getColumnListing($table) { $results = $this->connection->selectFromWriteConnection($this->grammar->compileColumnListing( $this->connection->getTablePrefix().$table )); return $this->connection->getPostProcessor()->processColumnListing($results); } /** * Modify a table on the schema. * * @param string $table * @param \Closure $callback * @return void */ public function table($table, Closure $callback) { $this->build($this->createBlueprint($table, $callback)); } /** * Create a new table on the schema. * * @param string $table * @param \Closure $callback * @return void */ public function create($table, Closure $callback) { $this->build(tap($this->createBlueprint($table), function ($blueprint) use ($callback) { $blueprint->create(); $callback($blueprint); })); } /** * Drop a table from the schema. * * @param string $table * @return void */ public function drop($table) { $this->build(tap($this->createBlueprint($table), function ($blueprint) { $blueprint->drop(); })); } /** * Drop a table from the schema if it exists. * * @param string $table * @return void */ public function dropIfExists($table) { $this->build(tap($this->createBlueprint($table), function ($blueprint) { $blueprint->dropIfExists(); })); } /** * Drop columns from a table schema. * * @param string $table * @param string|array $columns * @return void */ public function dropColumns($table, $columns) { $this->table($table, function (Blueprint $blueprint) use ($columns) { $blueprint->dropColumn($columns); }); } /** * Drop all tables from the database. * * @return void * * @throws \LogicException */ public function dropAllTables() { throw new LogicException('This database driver does not support dropping all tables.'); } /** * Drop all views from the database. * * @return void * * @throws \LogicException */ public function dropAllViews() { throw new LogicException('This database driver does not support dropping all views.'); } /** * Drop all types from the database. * * @return void * * @throws \LogicException */ public function dropAllTypes() { throw new LogicException('This database driver does not support dropping all types.'); } /** * Get all of the table names for the database. * * @return array * * @throws \LogicException */ public function getAllTables() { throw new LogicException('This database driver does not support getting all tables.'); } /** * Rename a table on the schema. * * @param string $from * @param string $to * @return void */ public function rename($from, $to) { $this->build(tap($this->createBlueprint($from), function ($blueprint) use ($to) { $blueprint->rename($to); })); } /** * Enable foreign key constraints. * * @return bool */ public function enableForeignKeyConstraints() { return $this->connection->statement( $this->grammar->compileEnableForeignKeyConstraints() ); } /** * Disable foreign key constraints. * * @return bool */ public function disableForeignKeyConstraints() { return $this->connection->statement( $this->grammar->compileDisableForeignKeyConstraints() ); } /** * Disable foreign key constraints during the execution of a callback. * * @param \Closure $callback * @return mixed */ public function withoutForeignKeyConstraints(Closure $callback) { $this->disableForeignKeyConstraints(); try { return $callback(); } finally { $this->enableForeignKeyConstraints(); } } /** * Execute the blueprint to build / modify the table. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return void */ protected function build(Blueprint $blueprint) { $blueprint->build($this->connection, $this->grammar); } /** * Create a new command set with a Closure. * * @param string $table * @param \Closure|null $callback * @return \Illuminate\Database\Schema\Blueprint */ protected function createBlueprint($table, Closure $callback = null) { $prefix = $this->connection->getConfig('prefix_indexes') ? $this->connection->getConfig('prefix') : ''; if (isset($this->resolver)) { return call_user_func($this->resolver, $table, $callback, $prefix); } return Container::getInstance()->make(Blueprint::class, compact('table', 'callback', 'prefix')); } /** * Get the database connection instance. * * @return \Illuminate\Database\Connection */ public function getConnection() { return $this->connection; } /** * Set the database connection instance. * * @param \Illuminate\Database\Connection $connection * @return $this */ public function setConnection(Connection $connection) { $this->connection = $connection; return $this; } /** * Set the Schema Blueprint resolver callback. * * @param \Closure $resolver * @return void */ public function blueprintResolver(Closure $resolver) { $this->resolver = $resolver; } } Schema/ColumnDefinition.php000064400000004547147536122140011742 0ustar00blueprint = $blueprint; } /** * Create a foreign key constraint on this column referencing the "id" column of the conventionally related table. * * @param string|null $table * @param string|null $column * @param string|null $indexName * @return \Illuminate\Database\Schema\ForeignKeyDefinition */ public function constrained($table = null, $column = 'id', $indexName = null) { return $this->references($column, $indexName)->on($table ?? Str::of($this->name)->beforeLast('_'.$column)->plural()); } /** * Specify which column this foreign ID references on another table. * * @param string $column * @param string $indexName * @return \Illuminate\Database\Schema\ForeignKeyDefinition */ public function references($column, $indexName = null) { return $this->blueprint->foreign($this->name, $indexName)->references($column); } } Schema/ForeignKeyDefinition.php000064400000003504147536122140012537 0ustar00onUpdate('cascade'); } /** * Indicate that updates should be restricted. * * @return $this */ public function restrictOnUpdate() { return $this->onUpdate('restrict'); } /** * Indicate that deletes should cascade. * * @return $this */ public function cascadeOnDelete() { return $this->onDelete('cascade'); } /** * Indicate that deletes should be restricted. * * @return $this */ public function restrictOnDelete() { return $this->onDelete('restrict'); } /** * Indicate that deletes should set the foreign key value to null. * * @return $this */ public function nullOnDelete() { return $this->onDelete('set null'); } /** * Indicate that deletes should have "no action". * * @return $this */ public function noActionOnDelete() { return $this->onDelete('no action'); } } Schema/Grammars/ChangeColumn.php000064400000016531147536122140012604 0ustar00isDoctrineAvailable()) { throw new RuntimeException(sprintf( 'Changing columns for table "%s" requires Doctrine DBAL. Please install the doctrine/dbal package.', $blueprint->getTable() )); } $schema = $connection->getDoctrineSchemaManager(); $databasePlatform = $connection->getDoctrineConnection()->getDatabasePlatform(); $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); $tableDiff = static::getChangedDiff( $grammar, $blueprint, $schema ); if (! $tableDiff->isEmpty()) { return (array) $databasePlatform->getAlterTableSQL($tableDiff); } return []; } /** * Get the Doctrine table difference for the given changes. * * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema * @return \Doctrine\DBAL\Schema\TableDiff */ protected static function getChangedDiff($grammar, Blueprint $blueprint, SchemaManager $schema) { $current = $schema->introspectTable($grammar->getTablePrefix().$blueprint->getTable()); return $schema->createComparator()->compareTables( $current, static::getTableWithColumnChanges($blueprint, $current) ); } /** * Get a copy of the given Doctrine table after making the column changes. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Doctrine\DBAL\Schema\Table $table * @return \Doctrine\DBAL\Schema\Table */ protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table) { $table = clone $table; foreach ($blueprint->getChangedColumns() as $fluent) { $column = static::getDoctrineColumn($table, $fluent); // Here we will spin through each fluent column definition and map it to the proper // Doctrine column definitions - which is necessary because Laravel and Doctrine // use some different terminology for various column attributes on the tables. foreach ($fluent->getAttributes() as $key => $value) { if (! is_null($option = static::mapFluentOptionToDoctrine($key))) { if (method_exists($column, $method = 'set'.ucfirst($option))) { $column->{$method}(static::mapFluentValueToDoctrine($option, $value)); continue; } $column->setPlatformOption($option, static::mapFluentValueToDoctrine($option, $value)); } } } return $table; } /** * Get the Doctrine column instance for a column change. * * @param \Doctrine\DBAL\Schema\Table $table * @param \Illuminate\Support\Fluent $fluent * @return \Doctrine\DBAL\Schema\Column */ protected static function getDoctrineColumn(Table $table, Fluent $fluent) { return $table->modifyColumn( $fluent['name'], static::getDoctrineColumnChangeOptions($fluent) )->getColumn($fluent['name']); } /** * Get the Doctrine column change options. * * @param \Illuminate\Support\Fluent $fluent * @return array */ protected static function getDoctrineColumnChangeOptions(Fluent $fluent) { $options = ['type' => static::getDoctrineColumnType($fluent['type'])]; if (! in_array($fluent['type'], ['smallint', 'integer', 'bigint'])) { $options['autoincrement'] = false; } if (in_array($fluent['type'], ['tinyText', 'text', 'mediumText', 'longText'])) { $options['length'] = static::calculateDoctrineTextLength($fluent['type']); } if ($fluent['type'] === 'char') { $options['fixed'] = true; } if (static::doesntNeedCharacterOptions($fluent['type'])) { $options['customSchemaOptions'] = [ 'collation' => '', 'charset' => '', ]; } return $options; } /** * Get the doctrine column type. * * @param string $type * @return \Doctrine\DBAL\Types\Type */ protected static function getDoctrineColumnType($type) { $type = strtolower($type); return Type::getType(match ($type) { 'biginteger' => 'bigint', 'smallinteger' => 'smallint', 'tinytext', 'mediumtext', 'longtext' => 'text', 'binary' => 'blob', 'uuid' => 'guid', 'char' => 'string', 'double' => 'float', default => $type, }); } /** * Calculate the proper column length to force the Doctrine text type. * * @param string $type * @return int */ protected static function calculateDoctrineTextLength($type) { return match ($type) { 'tinyText' => 1, 'mediumText' => 65535 + 1, 'longText' => 16777215 + 1, default => 255 + 1, }; } /** * Determine if the given type does not need character / collation options. * * @param string $type * @return bool */ protected static function doesntNeedCharacterOptions($type) { return in_array($type, [ 'bigInteger', 'binary', 'boolean', 'date', 'dateTime', 'decimal', 'double', 'float', 'integer', 'json', 'mediumInteger', 'smallInteger', 'time', 'timestamp', 'tinyInteger', ]); } /** * Get the matching Doctrine option for a given Fluent attribute name. * * @param string $attribute * @return string|null */ protected static function mapFluentOptionToDoctrine($attribute) { return match ($attribute) { 'type', 'name' => null, 'nullable' => 'notnull', 'total' => 'precision', 'places' => 'scale', default => $attribute, }; } /** * Get the matching Doctrine value for a given Fluent attribute. * * @param string $option * @param mixed $value * @return mixed */ protected static function mapFluentValueToDoctrine($option, $value) { return $option === 'notnull' ? ! $value : $value; } } Schema/Grammars/Grammar.php000075500000024451147536122140011632 0ustar00wrapTable($blueprint), $this->wrap($command->index) ); // Once we have the initial portion of the SQL statement we will add on the // key name, table name, and referenced columns. These will complete the // main portion of the SQL statement and this SQL will almost be done. $sql .= sprintf('foreign key (%s) references %s (%s)', $this->columnize($command->columns), $this->wrapTable($command->on), $this->columnize((array) $command->references) ); // Once we have the basic foreign key creation statement constructed we can // build out the syntax for what should happen on an update or delete of // the affected columns, which will get something like "cascade", etc. if (! is_null($command->onDelete)) { $sql .= " on delete {$command->onDelete}"; } if (! is_null($command->onUpdate)) { $sql .= " on update {$command->onUpdate}"; } return $sql; } /** * Compile the blueprint's added column definitions. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return array */ protected function getColumns(Blueprint $blueprint) { $columns = []; foreach ($blueprint->getAddedColumns() as $column) { // Each of the column types has their own compiler functions, which are tasked // with turning the column definition into its SQL format for this platform // used by the connection. The column's modifiers are compiled and added. $sql = $this->wrap($column).' '.$this->getType($column); $columns[] = $this->addModifiers($sql, $blueprint, $column); } return $columns; } /** * Get the SQL for the column data type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function getType(Fluent $column) { return $this->{'type'.ucfirst($column->type)}($column); } /** * Create the column definition for a generated, computed column type. * * @param \Illuminate\Support\Fluent $column * @return void * * @throws \RuntimeException */ protected function typeComputed(Fluent $column) { throw new RuntimeException('This database driver does not support the computed type.'); } /** * Add the column modifiers to the definition. * * @param string $sql * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string */ protected function addModifiers($sql, Blueprint $blueprint, Fluent $column) { foreach ($this->modifiers as $modifier) { if (method_exists($this, $method = "modify{$modifier}")) { $sql .= $this->{$method}($blueprint, $column); } } return $sql; } /** * Get the primary key command if it exists on the blueprint. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param string $name * @return \Illuminate\Support\Fluent|null */ protected function getCommandByName(Blueprint $blueprint, $name) { $commands = $this->getCommandsByName($blueprint, $name); if (count($commands) > 0) { return reset($commands); } } /** * Get all of the commands with a given name. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param string $name * @return array */ protected function getCommandsByName(Blueprint $blueprint, $name) { return array_filter($blueprint->getCommands(), function ($value) use ($name) { return $value->name == $name; }); } /** * Add a prefix to an array of values. * * @param string $prefix * @param array $values * @return array */ public function prefixArray($prefix, array $values) { return array_map(function ($value) use ($prefix) { return $prefix.' '.$value; }, $values); } /** * Wrap a table in keyword identifiers. * * @param mixed $table * @return string */ public function wrapTable($table) { return parent::wrapTable( $table instanceof Blueprint ? $table->getTable() : $table ); } /** * Wrap a value in keyword identifiers. * * @param \Illuminate\Support\Fluent|\Illuminate\Contracts\Database\Query\Expression|string $value * @param bool $prefixAlias * @return string */ public function wrap($value, $prefixAlias = false) { return parent::wrap( $value instanceof Fluent ? $value->name : $value, $prefixAlias ); } /** * Format a value so that it can be used in "default" clauses. * * @param mixed $value * @return string */ protected function getDefaultValue($value) { if ($value instanceof Expression) { return $this->getValue($value); } if ($value instanceof BackedEnum) { return "'{$value->value}'"; } return is_bool($value) ? "'".(int) $value."'" : "'".(string) $value."'"; } /** * Create an empty Doctrine DBAL TableDiff from the Blueprint. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema * @return \Doctrine\DBAL\Schema\TableDiff */ public function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema) { $tableName = $this->getTablePrefix().$blueprint->getTable(); $table = $schema->introspectTable($tableName); return new TableDiff(tableName: $tableName, fromTable: $table); } /** * Get the fluent commands for the grammar. * * @return array */ public function getFluentCommands() { return $this->fluentCommands; } /** * Check if this Grammar supports schema changes wrapped in a transaction. * * @return bool */ public function supportsSchemaTransactions() { return $this->transactions; } } Schema/Grammars/MySqlGrammar.php000075500000106541147536122140012621 0ustar00wrapValue($name), $this->wrapValue($connection->getConfig('charset')), $this->wrapValue($connection->getConfig('collation')), ); } /** * Compile a drop database if exists command. * * @param string $name * @return string */ public function compileDropDatabaseIfExists($name) { return sprintf( 'drop database if exists %s', $this->wrapValue($name) ); } /** * Compile the query to determine the list of tables. * * @return string */ public function compileTableExists() { return "select * from information_schema.tables where table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"; } /** * Compile the query to determine the list of columns. * * @return string */ public function compileColumnListing() { return 'select column_name as `column_name` from information_schema.columns where table_schema = ? and table_name = ?'; } /** * Compile a create table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return string */ public function compileCreate(Blueprint $blueprint, Fluent $command, Connection $connection) { $sql = $this->compileCreateTable( $blueprint, $command, $connection ); // Once we have the primary SQL, we can add the encoding option to the SQL for // the table. Then, we can check if a storage engine has been supplied for // the table. If so, we will add the engine declaration to the SQL query. $sql = $this->compileCreateEncoding( $sql, $connection, $blueprint ); // Finally, we will append the engine configuration onto this SQL statement as // the final thing we do before returning this finished SQL. Once this gets // added the query will be ready to execute against the real connections. return $this->compileCreateEngine($sql, $connection, $blueprint); } /** * Create the main create table clause. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return string */ protected function compileCreateTable($blueprint, $command, $connection) { return sprintf('%s table %s (%s)', $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)) ); } /** * Append the character set specifications to a command. * * @param string $sql * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return string */ protected function compileCreateEncoding($sql, Connection $connection, Blueprint $blueprint) { // First we will set the character set if one has been set on either the create // blueprint itself or on the root configuration for the connection that the // table is being created on. We will add these to the create table query. if (isset($blueprint->charset)) { $sql .= ' default character set '.$blueprint->charset; } elseif (! is_null($charset = $connection->getConfig('charset'))) { $sql .= ' default character set '.$charset; } // Next we will add the collation to the create table statement if one has been // added to either this create table blueprint or the configuration for this // connection that the query is targeting. We'll add it to this SQL query. if (isset($blueprint->collation)) { $sql .= " collate '{$blueprint->collation}'"; } elseif (! is_null($collation = $connection->getConfig('collation'))) { $sql .= " collate '{$collation}'"; } return $sql; } /** * Append the engine specifications to a command. * * @param string $sql * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return string */ protected function compileCreateEngine($sql, Connection $connection, Blueprint $blueprint) { if (isset($blueprint->engine)) { return $sql.' engine = '.$blueprint->engine; } elseif (! is_null($engine = $connection->getConfig('engine'))) { return $sql.' engine = '.$engine; } return $sql; } /** * Compile an add column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAdd(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('add', $this->getColumns($blueprint)); return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); } /** * Compile the auto-incrementing column starting values. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent $command) { if ($command->column->autoIncrement && $value = $command->column->get('startingValue', $command->column->get('from'))) { return 'alter table '.$this->wrapTable($blueprint).' auto_increment = '.$value; } } /** * Compile a rename column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { return $connection->usingNativeSchemaOperations() ? sprintf('alter table %s rename column %s to %s', $this->wrapTable($blueprint), $this->wrap($command->from), $this->wrap($command->to) ) : parent::compileRenameColumn($blueprint, $command, $connection); } /** * Compile a change column command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string * * @throws \RuntimeException */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { if (! $connection->usingNativeSchemaOperations()) { return parent::compileChange($blueprint, $command, $connection); } $columns = []; foreach ($blueprint->getChangedColumns() as $column) { $sql = sprintf('%s %s%s %s', is_null($column->renameTo) ? 'modify' : 'change', $this->wrap($column), is_null($column->renameTo) ? '' : ' '.$this->wrap($column->renameTo), $this->getType($column) ); $columns[] = $this->addModifiers($sql, $blueprint, $column); } return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); } /** * Compile a primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compilePrimary(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s add primary key %s(%s)', $this->wrapTable($blueprint), $command->algorithm ? 'using '.$command->algorithm : '', $this->columnize($command->columns) ); } /** * Compile a unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileUnique(Blueprint $blueprint, Fluent $command) { return $this->compileKey($blueprint, $command, 'unique'); } /** * Compile a plain index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileIndex(Blueprint $blueprint, Fluent $command) { return $this->compileKey($blueprint, $command, 'index'); } /** * Compile a fulltext index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileFullText(Blueprint $blueprint, Fluent $command) { return $this->compileKey($blueprint, $command, 'fulltext'); } /** * Compile a spatial index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) { return $this->compileKey($blueprint, $command, 'spatial index'); } /** * Compile an index creation command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param string $type * @return string */ protected function compileKey(Blueprint $blueprint, Fluent $command, $type) { return sprintf('alter table %s add %s %s%s(%s)', $this->wrapTable($blueprint), $type, $this->wrap($command->index), $command->algorithm ? ' using '.$command->algorithm : '', $this->columnize($command->columns) ); } /** * Compile a drop table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDrop(Blueprint $blueprint, Fluent $command) { return 'drop table '.$this->wrapTable($blueprint); } /** * Compile a drop table (if exists) command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { return 'drop table if exists '.$this->wrapTable($blueprint); } /** * Compile a drop column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropColumn(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('drop', $this->wrapArray($command->columns)); return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); } /** * Compile a drop primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) { return 'alter table '.$this->wrapTable($blueprint).' drop primary key'; } /** * Compile a drop unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop index {$index}"; } /** * Compile a drop index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop index {$index}"; } /** * Compile a drop fulltext index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropFullText(Blueprint $blueprint, Fluent $command) { return $this->compileDropIndex($blueprint, $command); } /** * Compile a drop spatial index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) { return $this->compileDropIndex($blueprint, $command); } /** * Compile a drop foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropForeign(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop foreign key {$index}"; } /** * Compile a rename table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRename(Blueprint $blueprint, Fluent $command) { $from = $this->wrapTable($blueprint); return "rename table {$from} to ".$this->wrapTable($command->to); } /** * Compile a rename index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s rename index %s to %s', $this->wrapTable($blueprint), $this->wrap($command->from), $this->wrap($command->to) ); } /** * Compile the SQL needed to drop all tables. * * @param array $tables * @return string */ public function compileDropAllTables($tables) { return 'drop table '.implode(',', $this->wrapArray($tables)); } /** * Compile the SQL needed to drop all views. * * @param array $views * @return string */ public function compileDropAllViews($views) { return 'drop view '.implode(',', $this->wrapArray($views)); } /** * Compile the SQL needed to retrieve all table names. * * @return string */ public function compileGetAllTables() { return 'SHOW FULL TABLES WHERE table_type = \'BASE TABLE\''; } /** * Compile the SQL needed to retrieve all view names. * * @return string */ public function compileGetAllViews() { return 'SHOW FULL TABLES WHERE table_type = \'VIEW\''; } /** * Compile the command to enable foreign key constraints. * * @return string */ public function compileEnableForeignKeyConstraints() { return 'SET FOREIGN_KEY_CHECKS=1;'; } /** * Compile the command to disable foreign key constraints. * * @return string */ public function compileDisableForeignKeyConstraints() { return 'SET FOREIGN_KEY_CHECKS=0;'; } /** * Compile a table comment command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileTableComment(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s comment = %s', $this->wrapTable($blueprint), "'".str_replace("'", "''", $command->comment)."'" ); } /** * Create the column definition for a char type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeChar(Fluent $column) { return "char({$column->length})"; } /** * Create the column definition for a string type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeString(Fluent $column) { return "varchar({$column->length})"; } /** * Create the column definition for a tiny text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyText(Fluent $column) { return 'tinytext'; } /** * Create the column definition for a text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeText(Fluent $column) { return 'text'; } /** * Create the column definition for a medium text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumText(Fluent $column) { return 'mediumtext'; } /** * Create the column definition for a long text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLongText(Fluent $column) { return 'longtext'; } /** * Create the column definition for a big integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBigInteger(Fluent $column) { return 'bigint'; } /** * Create the column definition for an integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeInteger(Fluent $column) { return 'int'; } /** * Create the column definition for a medium integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumInteger(Fluent $column) { return 'mediumint'; } /** * Create the column definition for a tiny integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyInteger(Fluent $column) { return 'tinyint'; } /** * Create the column definition for a small integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSmallInteger(Fluent $column) { return 'smallint'; } /** * Create the column definition for a float type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeFloat(Fluent $column) { return $this->typeDouble($column); } /** * Create the column definition for a double type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDouble(Fluent $column) { if ($column->total && $column->places) { return "double({$column->total}, {$column->places})"; } return 'double'; } /** * Create the column definition for a decimal type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDecimal(Fluent $column) { return "decimal({$column->total}, {$column->places})"; } /** * Create the column definition for a boolean type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBoolean(Fluent $column) { return 'tinyint(1)'; } /** * Create the column definition for an enumeration type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeEnum(Fluent $column) { return sprintf('enum(%s)', $this->quoteString($column->allowed)); } /** * Create the column definition for a set enumeration type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSet(Fluent $column) { return sprintf('set(%s)', $this->quoteString($column->allowed)); } /** * Create the column definition for a json type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJson(Fluent $column) { return 'json'; } /** * Create the column definition for a jsonb type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJsonb(Fluent $column) { return 'json'; } /** * Create the column definition for a date type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDate(Fluent $column) { return 'date'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTime(Fluent $column) { $current = $column->precision ? "CURRENT_TIMESTAMP($column->precision)" : 'CURRENT_TIMESTAMP'; if ($column->useCurrent) { $column->default(new Expression($current)); } if ($column->useCurrentOnUpdate) { $column->onUpdate(new Expression($current)); } return $column->precision ? "datetime($column->precision)" : 'datetime'; } /** * Create the column definition for a date-time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTimeTz(Fluent $column) { return $this->typeDateTime($column); } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTime(Fluent $column) { return $column->precision ? "time($column->precision)" : 'time'; } /** * Create the column definition for a time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimeTz(Fluent $column) { return $this->typeTime($column); } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestamp(Fluent $column) { $current = $column->precision ? "CURRENT_TIMESTAMP($column->precision)" : 'CURRENT_TIMESTAMP'; if ($column->useCurrent) { $column->default(new Expression($current)); } if ($column->useCurrentOnUpdate) { $column->onUpdate(new Expression($current)); } return $column->precision ? "timestamp($column->precision)" : 'timestamp'; } /** * Create the column definition for a timestamp (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestampTz(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a year type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeYear(Fluent $column) { return 'year'; } /** * Create the column definition for a binary type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBinary(Fluent $column) { return 'blob'; } /** * Create the column definition for a uuid type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeUuid(Fluent $column) { return 'char(36)'; } /** * Create the column definition for an IP address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeIpAddress(Fluent $column) { return 'varchar(45)'; } /** * Create the column definition for a MAC address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMacAddress(Fluent $column) { return 'varchar(17)'; } /** * Create the column definition for a spatial Geometry type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeGeometry(Fluent $column) { return 'geometry'; } /** * Create the column definition for a spatial Point type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typePoint(Fluent $column) { return 'point'; } /** * Create the column definition for a spatial LineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeLineString(Fluent $column) { return 'linestring'; } /** * Create the column definition for a spatial Polygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typePolygon(Fluent $column) { return 'polygon'; } /** * Create the column definition for a spatial GeometryCollection type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeGeometryCollection(Fluent $column) { return 'geometrycollection'; } /** * Create the column definition for a spatial MultiPoint type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiPoint(Fluent $column) { return 'multipoint'; } /** * Create the column definition for a spatial MultiLineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiLineString(Fluent $column) { return 'multilinestring'; } /** * Create the column definition for a spatial MultiPolygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiPolygon(Fluent $column) { return 'multipolygon'; } /** * Create the column definition for a generated, computed column type. * * @param \Illuminate\Support\Fluent $column * @return void * * @throws \RuntimeException */ protected function typeComputed(Fluent $column) { throw new RuntimeException('This database driver requires a type, see the virtualAs / storedAs modifiers.'); } /** * Get the SQL for a generated virtual column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) { if (! is_null($virtualAs = $column->virtualAsJson)) { if ($this->isJsonSelector($virtualAs)) { $virtualAs = $this->wrapJsonSelector($virtualAs); } return " as ({$virtualAs})"; } if (! is_null($virtualAs = $column->virtualAs)) { return " as ({$virtualAs})"; } } /** * Get the SQL for a generated stored column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) { if (! is_null($storedAs = $column->storedAsJson)) { if ($this->isJsonSelector($storedAs)) { $storedAs = $this->wrapJsonSelector($storedAs); } return " as ({$storedAs}) stored"; } if (! is_null($storedAs = $column->storedAs)) { return " as ({$storedAs}) stored"; } } /** * Get the SQL for an unsigned column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyUnsigned(Blueprint $blueprint, Fluent $column) { if ($column->unsigned) { return ' unsigned'; } } /** * Get the SQL for a character set column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCharset(Blueprint $blueprint, Fluent $column) { if (! is_null($column->charset)) { return ' character set '.$column->charset; } } /** * Get the SQL for a collation column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCollate(Blueprint $blueprint, Fluent $column) { if (! is_null($column->collation)) { return " collate '{$column->collation}'"; } } /** * Get the SQL for a nullable column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && is_null($column->storedAs) && is_null($column->storedAsJson)) { return $column->nullable ? ' null' : ' not null'; } if ($column->nullable === false) { return ' not null'; } } /** * Get the SQL for an invisible column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyInvisible(Blueprint $blueprint, Fluent $column) { if (! is_null($column->invisible)) { return ' invisible'; } } /** * Get the SQL for a default column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if (! is_null($column->default)) { return ' default '.$this->getDefaultValue($column->default); } } /** * Get the SQL for an "on update" column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyOnUpdate(Blueprint $blueprint, Fluent $column) { if (! is_null($column->onUpdate)) { return ' on update '.$this->getValue($column->onUpdate); } } /** * Get the SQL for an auto-increment column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (in_array($column->type, $this->serials) && $column->autoIncrement) { return ' auto_increment primary key'; } } /** * Get the SQL for a "first" column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyFirst(Blueprint $blueprint, Fluent $column) { if (! is_null($column->first)) { return ' first'; } } /** * Get the SQL for an "after" column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyAfter(Blueprint $blueprint, Fluent $column) { if (! is_null($column->after)) { return ' after '.$this->wrap($column->after); } } /** * Get the SQL for a "comment" column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyComment(Blueprint $blueprint, Fluent $column) { if (! is_null($column->comment)) { return " comment '".addslashes($column->comment)."'"; } } /** * Get the SQL for a SRID column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifySrid(Blueprint $blueprint, Fluent $column) { if (is_int($column->srid) && $column->srid > 0) { return ' srid '.$column->srid; } } /** * Wrap a single string in keyword identifiers. * * @param string $value * @return string */ protected function wrapValue($value) { if ($value !== '*') { return '`'.str_replace('`', '``', $value).'`'; } return $value; } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_unquote(json_extract('.$field.$path.'))'; } } Schema/Grammars/PostgresGrammar.php000075500000103706147536122140013362 0ustar00wrapValue($name), $this->wrapValue($connection->getConfig('charset')), ); } /** * Compile a drop database if exists command. * * @param string $name * @return string */ public function compileDropDatabaseIfExists($name) { return sprintf( 'drop database if exists %s', $this->wrapValue($name) ); } /** * Compile the query to determine if a table exists. * * @return string */ public function compileTableExists() { return "select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"; } /** * Compile the query to determine the list of columns. * * @return string */ public function compileColumnListing() { return 'select column_name from information_schema.columns where table_catalog = ? and table_schema = ? and table_name = ?'; } /** * Compile a create table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileCreate(Blueprint $blueprint, Fluent $command) { return sprintf('%s table %s (%s)', $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)) ); } /** * Compile a column addition command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAdd(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s %s', $this->wrapTable($blueprint), implode(', ', $this->prefixArray('add column', $this->getColumns($blueprint))) ); } /** * Compile the auto-incrementing column starting values. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent $command) { if ($command->column->autoIncrement && $value = $command->column->get('startingValue', $command->column->get('from'))) { return 'alter sequence '.$blueprint->getTable().'_'.$command->column->name.'_seq restart with '.$value; } } /** * Compile a rename column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { return $connection->usingNativeSchemaOperations() ? sprintf('alter table %s rename column %s to %s', $this->wrapTable($blueprint), $this->wrap($command->from), $this->wrap($command->to) ) : parent::compileRenameColumn($blueprint, $command, $connection); } /** * Compile a change column command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string * * @throws \RuntimeException */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { if (! $connection->usingNativeSchemaOperations()) { return parent::compileChange($blueprint, $command, $connection); } $columns = []; foreach ($blueprint->getChangedColumns() as $column) { $changes = ['type '.$this->getType($column).$this->modifyCollate($blueprint, $column)]; foreach ($this->modifiers as $modifier) { if ($modifier === 'Collate') { continue; } if (method_exists($this, $method = "modify{$modifier}")) { $constraints = (array) $this->{$method}($blueprint, $column); foreach ($constraints as $constraint) { $changes[] = $constraint; } } } $columns[] = implode(', ', $this->prefixArray('alter column '.$this->wrap($column), $changes)); } return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); } /** * Compile a primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compilePrimary(Blueprint $blueprint, Fluent $command) { $columns = $this->columnize($command->columns); return 'alter table '.$this->wrapTable($blueprint)." add primary key ({$columns})"; } /** * Compile a unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileUnique(Blueprint $blueprint, Fluent $command) { $sql = sprintf('alter table %s add constraint %s unique (%s)', $this->wrapTable($blueprint), $this->wrap($command->index), $this->columnize($command->columns) ); if (! is_null($command->deferrable)) { $sql .= $command->deferrable ? ' deferrable' : ' not deferrable'; } if ($command->deferrable && ! is_null($command->initiallyImmediate)) { $sql .= $command->initiallyImmediate ? ' initially immediate' : ' initially deferred'; } return $sql; } /** * Compile a plain index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileIndex(Blueprint $blueprint, Fluent $command) { return sprintf('create index %s on %s%s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $command->algorithm ? ' using '.$command->algorithm : '', $this->columnize($command->columns) ); } /** * Compile a fulltext index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string * * @throws \RuntimeException */ public function compileFulltext(Blueprint $blueprint, Fluent $command) { $language = $command->language ?: 'english'; $columns = array_map(function ($column) use ($language) { return "to_tsvector({$this->quoteString($language)}, {$this->wrap($column)})"; }, $command->columns); return sprintf('create index %s on %s using gin ((%s))', $this->wrap($command->index), $this->wrapTable($blueprint), implode(' || ', $columns) ); } /** * Compile a spatial index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) { $command->algorithm = 'gist'; return $this->compileIndex($blueprint, $command); } /** * Compile a foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileForeign(Blueprint $blueprint, Fluent $command) { $sql = parent::compileForeign($blueprint, $command); if (! is_null($command->deferrable)) { $sql .= $command->deferrable ? ' deferrable' : ' not deferrable'; } if ($command->deferrable && ! is_null($command->initiallyImmediate)) { $sql .= $command->initiallyImmediate ? ' initially immediate' : ' initially deferred'; } if (! is_null($command->notValid)) { $sql .= ' not valid'; } return $sql; } /** * Compile a drop table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDrop(Blueprint $blueprint, Fluent $command) { return 'drop table '.$this->wrapTable($blueprint); } /** * Compile a drop table (if exists) command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { return 'drop table if exists '.$this->wrapTable($blueprint); } /** * Compile the SQL needed to drop all tables. * * @param array $tables * @return string */ public function compileDropAllTables($tables) { return 'drop table '.implode(',', $this->escapeNames($tables)).' cascade'; } /** * Compile the SQL needed to drop all views. * * @param array $views * @return string */ public function compileDropAllViews($views) { return 'drop view '.implode(',', $this->escapeNames($views)).' cascade'; } /** * Compile the SQL needed to drop all types. * * @param array $types * @return string */ public function compileDropAllTypes($types) { return 'drop type '.implode(',', $this->escapeNames($types)).' cascade'; } /** * Compile the SQL needed to retrieve all table names. * * @param string|array $searchPath * @return string */ public function compileGetAllTables($searchPath) { return "select tablename, concat('\"', schemaname, '\".\"', tablename, '\"') as qualifiedname from pg_catalog.pg_tables where schemaname in ('".implode("','", (array) $searchPath)."')"; } /** * Compile the SQL needed to retrieve all view names. * * @param string|array $searchPath * @return string */ public function compileGetAllViews($searchPath) { return "select viewname, concat('\"', schemaname, '\".\"', viewname, '\"') as qualifiedname from pg_catalog.pg_views where schemaname in ('".implode("','", (array) $searchPath)."')"; } /** * Compile the SQL needed to retrieve all type names. * * @return string */ public function compileGetAllTypes() { return 'select distinct pg_type.typname from pg_type inner join pg_enum on pg_enum.enumtypid = pg_type.oid'; } /** * Compile a drop column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropColumn(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); } /** * Compile a drop primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) { $index = $this->wrap("{$blueprint->getPrefix()}{$blueprint->getTable()}_pkey"); return 'alter table '.$this->wrapTable($blueprint)." drop constraint {$index}"; } /** * Compile a drop unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; } /** * Compile a drop index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { return "drop index {$this->wrap($command->index)}"; } /** * Compile a drop fulltext index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropFullText(Blueprint $blueprint, Fluent $command) { return $this->compileDropIndex($blueprint, $command); } /** * Compile a drop spatial index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) { return $this->compileDropIndex($blueprint, $command); } /** * Compile a drop foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropForeign(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; } /** * Compile a rename table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRename(Blueprint $blueprint, Fluent $command) { $from = $this->wrapTable($blueprint); return "alter table {$from} rename to ".$this->wrapTable($command->to); } /** * Compile a rename index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command) { return sprintf('alter index %s rename to %s', $this->wrap($command->from), $this->wrap($command->to) ); } /** * Compile the command to enable foreign key constraints. * * @return string */ public function compileEnableForeignKeyConstraints() { return 'SET CONSTRAINTS ALL IMMEDIATE;'; } /** * Compile the command to disable foreign key constraints. * * @return string */ public function compileDisableForeignKeyConstraints() { return 'SET CONSTRAINTS ALL DEFERRED;'; } /** * Compile a comment command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileComment(Blueprint $blueprint, Fluent $command) { if (! is_null($comment = $command->column->comment) || $command->column->change) { return sprintf('comment on column %s.%s is %s', $this->wrapTable($blueprint), $this->wrap($command->column->name), is_null($comment) ? 'NULL' : "'".str_replace("'", "''", $comment)."'" ); } } /** * Compile a table comment command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileTableComment(Blueprint $blueprint, Fluent $command) { return sprintf('comment on table %s is %s', $this->wrapTable($blueprint), "'".str_replace("'", "''", $command->comment)."'" ); } /** * Quote-escape the given tables, views, or types. * * @param array $names * @return array */ public function escapeNames($names) { return array_map(static function ($name) { return '"'.collect(explode('.', $name)) ->map(fn ($segment) => trim($segment, '\'"')) ->implode('"."').'"'; }, $names); } /** * Create the column definition for a char type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeChar(Fluent $column) { if ($column->length) { return "char({$column->length})"; } return 'char'; } /** * Create the column definition for a string type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeString(Fluent $column) { if ($column->length) { return "varchar({$column->length})"; } return 'varchar'; } /** * Create the column definition for a tiny text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyText(Fluent $column) { return 'varchar(255)'; } /** * Create the column definition for a text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeText(Fluent $column) { return 'text'; } /** * Create the column definition for a medium text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumText(Fluent $column) { return 'text'; } /** * Create the column definition for a long text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLongText(Fluent $column) { return 'text'; } /** * Create the column definition for an integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeInteger(Fluent $column) { return $column->autoIncrement && is_null($column->generatedAs) ? 'serial' : 'integer'; } /** * Create the column definition for a big integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBigInteger(Fluent $column) { return $column->autoIncrement && is_null($column->generatedAs) ? 'bigserial' : 'bigint'; } /** * Create the column definition for a medium integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumInteger(Fluent $column) { return $this->typeInteger($column); } /** * Create the column definition for a tiny integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyInteger(Fluent $column) { return $this->typeSmallInteger($column); } /** * Create the column definition for a small integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSmallInteger(Fluent $column) { return $column->autoIncrement && is_null($column->generatedAs) ? 'smallserial' : 'smallint'; } /** * Create the column definition for a float type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeFloat(Fluent $column) { return $this->typeDouble($column); } /** * Create the column definition for a double type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDouble(Fluent $column) { return 'double precision'; } /** * Create the column definition for a real type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeReal(Fluent $column) { return 'real'; } /** * Create the column definition for a decimal type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDecimal(Fluent $column) { return "decimal({$column->total}, {$column->places})"; } /** * Create the column definition for a boolean type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBoolean(Fluent $column) { return 'boolean'; } /** * Create the column definition for an enumeration type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeEnum(Fluent $column) { return sprintf( 'varchar(255) check ("%s" in (%s))', $column->name, $this->quoteString($column->allowed) ); } /** * Create the column definition for a json type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJson(Fluent $column) { return 'json'; } /** * Create the column definition for a jsonb type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJsonb(Fluent $column) { return 'jsonb'; } /** * Create the column definition for a date type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDate(Fluent $column) { return 'date'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTime(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a date-time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTimeTz(Fluent $column) { return $this->typeTimestampTz($column); } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTime(Fluent $column) { return 'time'.(is_null($column->precision) ? '' : "($column->precision)").' without time zone'; } /** * Create the column definition for a time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimeTz(Fluent $column) { return 'time'.(is_null($column->precision) ? '' : "($column->precision)").' with time zone'; } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestamp(Fluent $column) { if ($column->useCurrent) { $column->default(new Expression('CURRENT_TIMESTAMP')); } return 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' without time zone'; } /** * Create the column definition for a timestamp (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestampTz(Fluent $column) { if ($column->useCurrent) { $column->default(new Expression('CURRENT_TIMESTAMP')); } return 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' with time zone'; } /** * Create the column definition for a year type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeYear(Fluent $column) { return $this->typeInteger($column); } /** * Create the column definition for a binary type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBinary(Fluent $column) { return 'bytea'; } /** * Create the column definition for a uuid type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeUuid(Fluent $column) { return 'uuid'; } /** * Create the column definition for an IP address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeIpAddress(Fluent $column) { return 'inet'; } /** * Create the column definition for a MAC address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMacAddress(Fluent $column) { return 'macaddr'; } /** * Create the column definition for a spatial Geometry type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeGeometry(Fluent $column) { return $this->formatPostGisType('geometry', $column); } /** * Create the column definition for a spatial Point type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typePoint(Fluent $column) { return $this->formatPostGisType('point', $column); } /** * Create the column definition for a spatial LineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLineString(Fluent $column) { return $this->formatPostGisType('linestring', $column); } /** * Create the column definition for a spatial Polygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typePolygon(Fluent $column) { return $this->formatPostGisType('polygon', $column); } /** * Create the column definition for a spatial GeometryCollection type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeGeometryCollection(Fluent $column) { return $this->formatPostGisType('geometrycollection', $column); } /** * Create the column definition for a spatial MultiPoint type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMultiPoint(Fluent $column) { return $this->formatPostGisType('multipoint', $column); } /** * Create the column definition for a spatial MultiLineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiLineString(Fluent $column) { return $this->formatPostGisType('multilinestring', $column); } /** * Create the column definition for a spatial MultiPolygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMultiPolygon(Fluent $column) { return $this->formatPostGisType('multipolygon', $column); } /** * Create the column definition for a spatial MultiPolygonZ type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMultiPolygonZ(Fluent $column) { return $this->formatPostGisType('multipolygonz', $column); } /** * Format the column definition for a PostGIS spatial type. * * @param string $type * @param \Illuminate\Support\Fluent $column * @return string */ private function formatPostGisType($type, Fluent $column) { if ($column->isGeometry === null) { return sprintf('geography(%s, %s)', $type, $column->projection ?? '4326'); } if ($column->projection !== null) { return sprintf('geometry(%s, %s)', $type, $column->projection); } return "geometry({$type})"; } /** * Get the SQL for a collation column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCollate(Blueprint $blueprint, Fluent $column) { if (! is_null($column->collation)) { return ' collate '.$this->wrapValue($column->collation); } } /** * Get the SQL for a nullable column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { if ($column->change) { return $column->nullable ? 'drop not null' : 'set not null'; } return $column->nullable ? ' null' : ' not null'; } /** * Get the SQL for a default column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if ($column->change) { return is_null($column->default) ? 'drop default' : 'set default '.$this->getDefaultValue($column->default); } if (! is_null($column->default)) { return ' default '.$this->getDefaultValue($column->default); } } /** * Get the SQL for an auto-increment column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (! $column->change && (in_array($column->type, $this->serials) || ($column->generatedAs !== null)) && $column->autoIncrement) { return ' primary key'; } } /** * Get the SQL for a generated virtual column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) { if ($column->change) { if (array_key_exists('virtualAs', $column->getAttributes())) { return is_null($column->virtualAs) ? 'drop expression if exists' : throw new LogicException('This database driver does not support modifying generated columns.'); } return null; } if (! is_null($column->virtualAs)) { return " generated always as ({$column->virtualAs})"; } } /** * Get the SQL for a generated stored column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) { if ($column->change) { if (array_key_exists('storedAs', $column->getAttributes())) { return is_null($column->storedAs) ? 'drop expression if exists' : throw new LogicException('This database driver does not support modifying generated columns.'); } return null; } if (! is_null($column->storedAs)) { return " generated always as ({$column->storedAs}) stored"; } } /** * Get the SQL for an identity column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|array|null */ protected function modifyGeneratedAs(Blueprint $blueprint, Fluent $column) { $sql = null; if (! is_null($column->generatedAs)) { $sql = sprintf( ' generated %s as identity%s', $column->always ? 'always' : 'by default', ! is_bool($column->generatedAs) && ! empty($column->generatedAs) ? " ({$column->generatedAs})" : '' ); } if ($column->change) { $changes = ['drop identity if exists']; if (! is_null($sql)) { $changes[] = 'add '.$sql; } return $changes; } return $sql; } } Schema/Grammars/RenameColumn.php000064400000005713147536122140012626 0ustar00getDoctrineSchemaManager(); $databasePlatform = $connection->getDoctrineConnection()->getDatabasePlatform(); $databasePlatform->registerDoctrineTypeMapping('enum', 'string'); $column = $connection->getDoctrineColumn( $grammar->getTablePrefix().$blueprint->getTable(), $command->from ); return (array) $databasePlatform->getAlterTableSQL(static::getRenamedDiff( $grammar, $blueprint, $command, $column, $schema )); } /** * Get a new column instance with the new column name. * * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Doctrine\DBAL\Schema\Column $column * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema * @return \Doctrine\DBAL\Schema\TableDiff */ protected static function getRenamedDiff(Grammar $grammar, Blueprint $blueprint, Fluent $command, Column $column, SchemaManager $schema) { return static::setRenamedColumns( $grammar->getDoctrineTableDiff($blueprint, $schema), $command, $column ); } /** * Set the renamed columns on the table diff. * * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff * @param \Illuminate\Support\Fluent $command * @param \Doctrine\DBAL\Schema\Column $column * @return \Doctrine\DBAL\Schema\TableDiff */ protected static function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) { $tableDiff->renamedColumns = [ $command->from => new Column($command->to, $column->getType(), self::getWritableColumnOptions($column)), ]; return $tableDiff; } /** * Get the writable column options. * * @param \Doctrine\DBAL\Schema\Column $column * @return array */ private static function getWritableColumnOptions(Column $column) { return array_filter($column->toArray(), function (string $name) use ($column) { return method_exists($column, 'set'.$name); }, ARRAY_FILTER_USE_KEY); } } Schema/Grammars/SQLiteGrammar.php000075500000065744147536122140012726 0ustar00wrap(str_replace('.', '__', $table)).')'; } /** * Compile a create table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileCreate(Blueprint $blueprint, Fluent $command) { return sprintf('%s table %s (%s%s%s)', $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)), (string) $this->addForeignKeys($blueprint), (string) $this->addPrimaryKeys($blueprint) ); } /** * Get the foreign key syntax for a table creation statement. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return string|null */ protected function addForeignKeys(Blueprint $blueprint) { $foreigns = $this->getCommandsByName($blueprint, 'foreign'); return collect($foreigns)->reduce(function ($sql, $foreign) { // Once we have all the foreign key commands for the table creation statement // we'll loop through each of them and add them to the create table SQL we // are building, since SQLite needs foreign keys on the tables creation. $sql .= $this->getForeignKey($foreign); if (! is_null($foreign->onDelete)) { $sql .= " on delete {$foreign->onDelete}"; } // If this foreign key specifies the action to be taken on update we will add // that to the statement here. We'll append it to this SQL and then return // the SQL so we can keep adding any other foreign constraints onto this. if (! is_null($foreign->onUpdate)) { $sql .= " on update {$foreign->onUpdate}"; } return $sql; }, ''); } /** * Get the SQL for the foreign key. * * @param \Illuminate\Support\Fluent $foreign * @return string */ protected function getForeignKey($foreign) { // We need to columnize the columns that the foreign key is being defined for // so that it is a properly formatted list. Once we have done this, we can // return the foreign key SQL declaration to the calling method for use. return sprintf(', foreign key(%s) references %s(%s)', $this->columnize($foreign->columns), $this->wrapTable($foreign->on), $this->columnize((array) $foreign->references) ); } /** * Get the primary key syntax for a table creation statement. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return string|null */ protected function addPrimaryKeys(Blueprint $blueprint) { if (! is_null($primary = $this->getCommandByName($blueprint, 'primary'))) { return ", primary key ({$this->columnize($primary->columns)})"; } } /** * Compile alter table commands for adding columns. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return array */ public function compileAdd(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); return collect($columns)->reject(function ($column) { return preg_match('/as \(.*\) stored/', $column) > 0; })->map(function ($column) use ($blueprint) { return 'alter table '.$this->wrapTable($blueprint).' '.$column; })->all(); } /** * Compile a rename column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { return $connection->usingNativeSchemaOperations() ? sprintf('alter table %s rename column %s to %s', $this->wrapTable($blueprint), $this->wrap($command->from), $this->wrap($command->to) ) : parent::compileRenameColumn($blueprint, $command, $connection); } /** * Compile a unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileUnique(Blueprint $blueprint, Fluent $command) { return sprintf('create unique index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a plain index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileIndex(Blueprint $blueprint, Fluent $command) { return sprintf('create index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a spatial index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return void * * @throws \RuntimeException */ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) { throw new RuntimeException('The database driver in use does not support spatial indexes.'); } /** * Compile a foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string|null */ public function compileForeign(Blueprint $blueprint, Fluent $command) { // Handled on table creation... } /** * Compile a drop table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDrop(Blueprint $blueprint, Fluent $command) { return 'drop table '.$this->wrapTable($blueprint); } /** * Compile a drop table (if exists) command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { return 'drop table if exists '.$this->wrapTable($blueprint); } /** * Compile the SQL needed to drop all tables. * * @return string */ public function compileDropAllTables() { return "delete from sqlite_master where type in ('table', 'index', 'trigger')"; } /** * Compile the SQL needed to drop all views. * * @return string */ public function compileDropAllViews() { return "delete from sqlite_master where type in ('view')"; } /** * Compile the SQL needed to retrieve all table names. * * @return string */ public function compileGetAllTables() { return 'select type, name from sqlite_master where type = \'table\' and name not like \'sqlite_%\''; } /** * Compile the SQL needed to retrieve all view names. * * @return string */ public function compileGetAllViews() { return 'select type, name from sqlite_master where type = \'view\''; } /** * Compile the SQL needed to rebuild the database. * * @return string */ public function compileRebuild() { return 'vacuum'; } /** * Compile a drop column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array */ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { if ($connection->usingNativeSchemaOperations()) { $table = $this->wrapTable($blueprint); $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column )->all(); } else { $tableDiff = $this->getDoctrineTableDiff( $blueprint, $schema = $connection->getDoctrineSchemaManager() ); foreach ($command->columns as $name) { $tableDiff->removedColumns[$name] = $connection->getDoctrineColumn( $this->getTablePrefix().$blueprint->getTable(), $name ); } return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); } } /** * Compile a drop unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index}"; } /** * Compile a drop index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index}"; } /** * Compile a drop spatial index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return void * * @throws \RuntimeException */ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) { throw new RuntimeException('The database driver in use does not support spatial indexes.'); } /** * Compile a rename table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRename(Blueprint $blueprint, Fluent $command) { $from = $this->wrapTable($blueprint); return "alter table {$from} rename to ".$this->wrapTable($command->to); } /** * Compile a rename index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array * * @throws \RuntimeException */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connection $connection) { $schemaManager = $connection->getDoctrineSchemaManager(); $indexes = $schemaManager->listTableIndexes($this->getTablePrefix().$blueprint->getTable()); $index = Arr::get($indexes, $command->from); if (! $index) { throw new RuntimeException("Index [{$command->from}] does not exist."); } $newIndex = new Index( $command->to, $index->getColumns(), $index->isUnique(), $index->isPrimary(), $index->getFlags(), $index->getOptions() ); $platform = $connection->getDoctrineConnection()->getDatabasePlatform(); return [ $platform->getDropIndexSQL($command->from, $this->getTablePrefix().$blueprint->getTable()), $platform->getCreateIndexSQL($newIndex, $this->getTablePrefix().$blueprint->getTable()), ]; } /** * Compile the command to enable foreign key constraints. * * @return string */ public function compileEnableForeignKeyConstraints() { return 'PRAGMA foreign_keys = ON;'; } /** * Compile the command to disable foreign key constraints. * * @return string */ public function compileDisableForeignKeyConstraints() { return 'PRAGMA foreign_keys = OFF;'; } /** * Compile the SQL needed to enable a writable schema. * * @return string */ public function compileEnableWriteableSchema() { return 'PRAGMA writable_schema = 1;'; } /** * Compile the SQL needed to disable a writable schema. * * @return string */ public function compileDisableWriteableSchema() { return 'PRAGMA writable_schema = 0;'; } /** * Create the column definition for a char type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeChar(Fluent $column) { return 'varchar'; } /** * Create the column definition for a string type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeString(Fluent $column) { return 'varchar'; } /** * Create the column definition for a tiny text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyText(Fluent $column) { return 'text'; } /** * Create the column definition for a text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeText(Fluent $column) { return 'text'; } /** * Create the column definition for a medium text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumText(Fluent $column) { return 'text'; } /** * Create the column definition for a long text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLongText(Fluent $column) { return 'text'; } /** * Create the column definition for an integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a big integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBigInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a medium integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a tiny integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a small integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSmallInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a float type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeFloat(Fluent $column) { return 'float'; } /** * Create the column definition for a double type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDouble(Fluent $column) { return 'float'; } /** * Create the column definition for a decimal type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDecimal(Fluent $column) { return 'numeric'; } /** * Create the column definition for a boolean type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBoolean(Fluent $column) { return 'tinyint(1)'; } /** * Create the column definition for an enumeration type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeEnum(Fluent $column) { return sprintf( 'varchar check ("%s" in (%s))', $column->name, $this->quoteString($column->allowed) ); } /** * Create the column definition for a json type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJson(Fluent $column) { return 'text'; } /** * Create the column definition for a jsonb type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJsonb(Fluent $column) { return 'text'; } /** * Create the column definition for a date type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDate(Fluent $column) { return 'date'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTime(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a date-time (with time zone) type. * * Note: "SQLite does not have a storage class set aside for storing dates and/or times." * * @link https://www.sqlite.org/datatype3.html * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTimeTz(Fluent $column) { return $this->typeDateTime($column); } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTime(Fluent $column) { return 'time'; } /** * Create the column definition for a time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimeTz(Fluent $column) { return $this->typeTime($column); } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestamp(Fluent $column) { if ($column->useCurrent) { $column->default(new Expression('CURRENT_TIMESTAMP')); } return 'datetime'; } /** * Create the column definition for a timestamp (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestampTz(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a year type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeYear(Fluent $column) { return $this->typeInteger($column); } /** * Create the column definition for a binary type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBinary(Fluent $column) { return 'blob'; } /** * Create the column definition for a uuid type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeUuid(Fluent $column) { return 'varchar'; } /** * Create the column definition for an IP address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeIpAddress(Fluent $column) { return 'varchar'; } /** * Create the column definition for a MAC address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMacAddress(Fluent $column) { return 'varchar'; } /** * Create the column definition for a spatial Geometry type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeGeometry(Fluent $column) { return 'geometry'; } /** * Create the column definition for a spatial Point type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typePoint(Fluent $column) { return 'point'; } /** * Create the column definition for a spatial LineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeLineString(Fluent $column) { return 'linestring'; } /** * Create the column definition for a spatial Polygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typePolygon(Fluent $column) { return 'polygon'; } /** * Create the column definition for a spatial GeometryCollection type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeGeometryCollection(Fluent $column) { return 'geometrycollection'; } /** * Create the column definition for a spatial MultiPoint type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiPoint(Fluent $column) { return 'multipoint'; } /** * Create the column definition for a spatial MultiLineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiLineString(Fluent $column) { return 'multilinestring'; } /** * Create the column definition for a spatial MultiPolygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiPolygon(Fluent $column) { return 'multipolygon'; } /** * Create the column definition for a generated, computed column type. * * @param \Illuminate\Support\Fluent $column * @return void * * @throws \RuntimeException */ protected function typeComputed(Fluent $column) { throw new RuntimeException('This database driver requires a type, see the virtualAs / storedAs modifiers.'); } /** * Get the SQL for a generated virtual column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) { if (! is_null($virtualAs = $column->virtualAsJson)) { if ($this->isJsonSelector($virtualAs)) { $virtualAs = $this->wrapJsonSelector($virtualAs); } return " as ({$virtualAs})"; } if (! is_null($virtualAs = $column->virtualAs)) { return " as ({$virtualAs})"; } } /** * Get the SQL for a generated stored column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) { if (! is_null($storedAs = $column->storedAsJson)) { if ($this->isJsonSelector($storedAs)) { $storedAs = $this->wrapJsonSelector($storedAs); } return " as ({$storedAs}) stored"; } if (! is_null($storedAs = $column->storedAs)) { return " as ({$column->storedAs}) stored"; } } /** * Get the SQL for a nullable column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && is_null($column->storedAs) && is_null($column->storedAsJson)) { return $column->nullable ? '' : ' not null'; } if ($column->nullable === false) { return ' not null'; } } /** * Get the SQL for a default column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if (! is_null($column->default) && is_null($column->virtualAs) && is_null($column->virtualAsJson) && is_null($column->storedAs)) { return ' default '.$this->getDefaultValue($column->default); } } /** * Get the SQL for an auto-increment column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (in_array($column->type, $this->serials) && $column->autoIncrement) { return ' primary key autoincrement'; } } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_extract('.$field.$path.')'; } } Schema/Grammars/SqlServerGrammar.php000075500000067724147536122140013513 0ustar00wrapValue($name), ); } /** * Compile a drop database if exists command. * * @param string $name * @return string */ public function compileDropDatabaseIfExists($name) { return sprintf( 'drop database if exists %s', $this->wrapValue($name) ); } /** * Compile the query to determine if a table exists. * * @return string */ public function compileTableExists() { return "select * from sys.sysobjects where id = object_id(?) and xtype in ('U', 'V')"; } /** * Compile the query to determine the list of columns. * * @param string $table * @return string */ public function compileColumnListing($table) { return "select name from sys.columns where object_id = object_id('$table')"; } /** * Compile a create table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileCreate(Blueprint $blueprint, Fluent $command) { $columns = implode(', ', $this->getColumns($blueprint)); return 'create table '.$this->wrapTable($blueprint)." ($columns)"; } /** * Compile a column addition table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAdd(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s add %s', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)) ); } /** * Compile a rename column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string */ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { return $connection->usingNativeSchemaOperations() ? sprintf("sp_rename '%s', %s, 'COLUMN'", $this->wrap($blueprint->getTable().'.'.$command->from), $this->wrap($command->to) ) : parent::compileRenameColumn($blueprint, $command, $connection); } /** * Compile a change column command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string * * @throws \RuntimeException */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { if (! $connection->usingNativeSchemaOperations()) { return parent::compileChange($blueprint, $command, $connection); } $changes = [$this->compileDropDefaultConstraint($blueprint, $command)]; foreach ($blueprint->getChangedColumns() as $column) { $sql = sprintf('alter table %s alter column %s %s', $this->wrapTable($blueprint), $this->wrap($column), $this->getType($column) ); foreach ($this->modifiers as $modifier) { if (method_exists($this, $method = "modify{$modifier}")) { $sql .= $this->{$method}($blueprint, $column); } } $changes[] = $sql; } return $changes; } /** * Compile a primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compilePrimary(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s add constraint %s primary key (%s)', $this->wrapTable($blueprint), $this->wrap($command->index), $this->columnize($command->columns) ); } /** * Compile a unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileUnique(Blueprint $blueprint, Fluent $command) { return sprintf('create unique index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a plain index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileIndex(Blueprint $blueprint, Fluent $command) { return sprintf('create index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a spatial index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) { return sprintf('create spatial index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a default command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string|null */ public function compileDefault(Blueprint $blueprint, Fluent $command) { if ($command->column->change && ! is_null($command->column->default)) { return sprintf('alter table %s add default %s for %s', $this->wrapTable($blueprint), $this->getDefaultValue($command->column->default), $this->wrap($command->column) ); } } /** * Compile a drop table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDrop(Blueprint $blueprint, Fluent $command) { return 'drop table '.$this->wrapTable($blueprint); } /** * Compile a drop table (if exists) command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { return sprintf('if exists (select * from sys.sysobjects where id = object_id(%s, \'U\')) drop table %s', "'".str_replace("'", "''", $this->getTablePrefix().$blueprint->getTable())."'", $this->wrapTable($blueprint) ); } /** * Compile the SQL needed to drop all tables. * * @return string */ public function compileDropAllTables() { return "EXEC sp_msforeachtable 'DROP TABLE ?'"; } /** * Compile a drop column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropColumn(Blueprint $blueprint, Fluent $command) { $columns = $this->wrapArray($command->columns); $dropExistingConstraintsSql = $this->compileDropDefaultConstraint($blueprint, $command).';'; return $dropExistingConstraintsSql.'alter table '.$this->wrapTable($blueprint).' drop column '.implode(', ', $columns); } /** * Compile a drop default constraint command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $command) { $columns = $command->name === 'change' ? "'".collect($blueprint->getChangedColumns())->pluck('name')->implode("','")."'" : "'".implode("','", $command->columns)."'"; $tableName = $this->getTablePrefix().$blueprint->getTable(); $sql = "DECLARE @sql NVARCHAR(MAX) = '';"; $sql .= "SELECT @sql += 'ALTER TABLE [dbo].[{$tableName}] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; $sql .= 'FROM sys.columns '; $sql .= "WHERE [object_id] = OBJECT_ID('[dbo].[{$tableName}]') AND [name] in ({$columns}) AND [default_object_id] <> 0;"; $sql .= 'EXEC(@sql)'; return $sql; } /** * Compile a drop primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; } /** * Compile a drop unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index} on {$this->wrapTable($blueprint)}"; } /** * Compile a drop index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index} on {$this->wrapTable($blueprint)}"; } /** * Compile a drop spatial index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) { return $this->compileDropIndex($blueprint, $command); } /** * Compile a drop foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropForeign(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "alter table {$this->wrapTable($blueprint)} drop constraint {$index}"; } /** * Compile a rename table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRename(Blueprint $blueprint, Fluent $command) { $from = $this->wrapTable($blueprint); return "sp_rename {$from}, ".$this->wrapTable($command->to); } /** * Compile a rename index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command) { return sprintf("sp_rename N'%s', %s, N'INDEX'", $this->wrap($blueprint->getTable().'.'.$command->from), $this->wrap($command->to) ); } /** * Compile the command to enable foreign key constraints. * * @return string */ public function compileEnableForeignKeyConstraints() { return 'EXEC sp_msforeachtable @command1="print \'?\'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all";'; } /** * Compile the command to disable foreign key constraints. * * @return string */ public function compileDisableForeignKeyConstraints() { return 'EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all";'; } /** * Compile the command to drop all foreign keys. * * @return string */ public function compileDropAllForeignKeys() { return "DECLARE @sql NVARCHAR(MAX) = N''; SELECT @sql += 'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + + QUOTENAME(OBJECT_NAME(parent_object_id)) + ' DROP CONSTRAINT ' + QUOTENAME(name) + ';' FROM sys.foreign_keys; EXEC sp_executesql @sql;"; } /** * Compile the command to drop all views. * * @return string */ public function compileDropAllViews() { return "DECLARE @sql NVARCHAR(MAX) = N''; SELECT @sql += 'DROP VIEW ' + QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' + QUOTENAME(name) + ';' FROM sys.views; EXEC sp_executesql @sql;"; } /** * Compile the SQL needed to retrieve all table names. * * @return string */ public function compileGetAllTables() { return "select name, type from sys.tables where type = 'U'"; } /** * Compile the SQL needed to retrieve all view names. * * @return string */ public function compileGetAllViews() { return "select name, type from sys.objects where type = 'V'"; } /** * Create the column definition for a char type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeChar(Fluent $column) { return "nchar({$column->length})"; } /** * Create the column definition for a string type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeString(Fluent $column) { return "nvarchar({$column->length})"; } /** * Create the column definition for a tiny text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyText(Fluent $column) { return 'nvarchar(255)'; } /** * Create the column definition for a text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeText(Fluent $column) { return 'nvarchar(max)'; } /** * Create the column definition for a medium text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumText(Fluent $column) { return 'nvarchar(max)'; } /** * Create the column definition for a long text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLongText(Fluent $column) { return 'nvarchar(max)'; } /** * Create the column definition for an integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeInteger(Fluent $column) { return 'int'; } /** * Create the column definition for a big integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBigInteger(Fluent $column) { return 'bigint'; } /** * Create the column definition for a medium integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumInteger(Fluent $column) { return 'int'; } /** * Create the column definition for a tiny integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyInteger(Fluent $column) { return 'tinyint'; } /** * Create the column definition for a small integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSmallInteger(Fluent $column) { return 'smallint'; } /** * Create the column definition for a float type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeFloat(Fluent $column) { return 'float'; } /** * Create the column definition for a double type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDouble(Fluent $column) { return 'float'; } /** * Create the column definition for a decimal type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDecimal(Fluent $column) { return "decimal({$column->total}, {$column->places})"; } /** * Create the column definition for a boolean type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBoolean(Fluent $column) { return 'bit'; } /** * Create the column definition for an enumeration type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeEnum(Fluent $column) { return sprintf( 'nvarchar(255) check ("%s" in (%s))', $column->name, $this->quoteString($column->allowed) ); } /** * Create the column definition for a json type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJson(Fluent $column) { return 'nvarchar(max)'; } /** * Create the column definition for a jsonb type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJsonb(Fluent $column) { return 'nvarchar(max)'; } /** * Create the column definition for a date type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDate(Fluent $column) { return 'date'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTime(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a date-time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTimeTz(Fluent $column) { return $this->typeTimestampTz($column); } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTime(Fluent $column) { return $column->precision ? "time($column->precision)" : 'time'; } /** * Create the column definition for a time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimeTz(Fluent $column) { return $this->typeTime($column); } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestamp(Fluent $column) { if ($column->useCurrent) { $column->default(new Expression('CURRENT_TIMESTAMP')); } return $column->precision ? "datetime2($column->precision)" : 'datetime'; } /** * Create the column definition for a timestamp (with time zone) type. * * @link https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetimeoffset-transact-sql?view=sql-server-ver15 * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestampTz(Fluent $column) { if ($column->useCurrent) { $column->default(new Expression('CURRENT_TIMESTAMP')); } return $column->precision ? "datetimeoffset($column->precision)" : 'datetimeoffset'; } /** * Create the column definition for a year type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeYear(Fluent $column) { return $this->typeInteger($column); } /** * Create the column definition for a binary type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBinary(Fluent $column) { return 'varbinary(max)'; } /** * Create the column definition for a uuid type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeUuid(Fluent $column) { return 'uniqueidentifier'; } /** * Create the column definition for an IP address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeIpAddress(Fluent $column) { return 'nvarchar(45)'; } /** * Create the column definition for a MAC address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMacAddress(Fluent $column) { return 'nvarchar(17)'; } /** * Create the column definition for a spatial Geometry type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeGeometry(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial Point type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typePoint(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial LineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeLineString(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial Polygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typePolygon(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial GeometryCollection type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeGeometryCollection(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial MultiPoint type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiPoint(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial MultiLineString type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiLineString(Fluent $column) { return 'geography'; } /** * Create the column definition for a spatial MultiPolygon type. * * @param \Illuminate\Support\Fluent $column * @return string */ public function typeMultiPolygon(Fluent $column) { return 'geography'; } /** * Create the column definition for a generated, computed column type. * * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function typeComputed(Fluent $column) { return "as ({$column->expression})"; } /** * Get the SQL for a collation column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCollate(Blueprint $blueprint, Fluent $column) { if (! is_null($column->collation)) { return ' collate '.$column->collation; } } /** * Get the SQL for a nullable column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { if ($column->type !== 'computed') { return $column->nullable ? ' null' : ' not null'; } } /** * Get the SQL for a default column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if (! $column->change && ! is_null($column->default)) { return ' default '.$this->getDefaultValue($column->default); } } /** * Get the SQL for an auto-increment column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (! $column->change && in_array($column->type, $this->serials) && $column->autoIncrement) { return ' identity primary key'; } } /** * Get the SQL for a generated stored column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyPersisted(Blueprint $blueprint, Fluent $column) { if ($column->change) { if ($column->type === 'computed') { return $column->persisted ? ' add persisted' : ' drop persisted'; } return null; } if ($column->persisted) { return ' persisted'; } } /** * Wrap a table in keyword identifiers. * * @param \Illuminate\Database\Schema\Blueprint|\Illuminate\Contracts\Database\Query\Expression|string $table * @return string */ public function wrapTable($table) { if ($table instanceof Blueprint && $table->temporary) { $this->setTablePrefix('#'); } return parent::wrapTable($table); } /** * 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 "N'$value'"; } } Schema/IndexDefinition.php000064400000001072147536122140011542 0ustar00connection->statement( $this->grammar->compileCreateDatabase($name, $this->connection) ); } /** * Drop a database from the schema if the database exists. * * @param string $name * @return bool */ public function dropDatabaseIfExists($name) { return $this->connection->statement( $this->grammar->compileDropDatabaseIfExists($name) ); } /** * Determine if the given table exists. * * @param string $table * @return bool */ public function hasTable($table) { $table = $this->connection->getTablePrefix().$table; return count($this->connection->selectFromWriteConnection( $this->grammar->compileTableExists(), [$this->connection->getDatabaseName(), $table] )) > 0; } /** * Get the column listing for a given table. * * @param string $table * @return array */ public function getColumnListing($table) { $table = $this->connection->getTablePrefix().$table; $results = $this->connection->selectFromWriteConnection( $this->grammar->compileColumnListing(), [$this->connection->getDatabaseName(), $table] ); return $this->connection->getPostProcessor()->processColumnListing($results); } /** * Drop all tables from the database. * * @return void */ public function dropAllTables() { $tables = []; foreach ($this->getAllTables() as $row) { $row = (array) $row; $tables[] = reset($row); } if (empty($tables)) { return; } $this->disableForeignKeyConstraints(); $this->connection->statement( $this->grammar->compileDropAllTables($tables) ); $this->enableForeignKeyConstraints(); } /** * Drop all views from the database. * * @return void */ public function dropAllViews() { $views = []; foreach ($this->getAllViews() as $row) { $row = (array) $row; $views[] = reset($row); } if (empty($views)) { return; } $this->connection->statement( $this->grammar->compileDropAllViews($views) ); } /** * Get all of the table names for the database. * * @return array */ public function getAllTables() { return $this->connection->select( $this->grammar->compileGetAllTables() ); } /** * Get all of the view names for the database. * * @return array */ public function getAllViews() { return $this->connection->select( $this->grammar->compileGetAllViews() ); } } Schema/MySqlSchemaState.php000064400000012461147536122140011655 0ustar00executeDumpProcess($this->makeProcess( $this->baseDumpCommand().' --routines --result-file="${:LARAVEL_LOAD_PATH}" --no-data' ), $this->output, array_merge($this->baseVariables($this->connection->getConfig()), [ 'LARAVEL_LOAD_PATH' => $path, ])); $this->removeAutoIncrementingState($path); $this->appendMigrationData($path); } /** * Remove the auto-incrementing state from the given schema dump. * * @param string $path * @return void */ protected function removeAutoIncrementingState(string $path) { $this->files->put($path, preg_replace( '/\s+AUTO_INCREMENT=[0-9]+/iu', '', $this->files->get($path) )); } /** * Append the migration data to the schema dump. * * @param string $path * @return void */ protected function appendMigrationData(string $path) { $process = $this->executeDumpProcess($this->makeProcess( $this->baseDumpCommand().' '.$this->migrationTable.' --no-create-info --skip-extended-insert --skip-routines --compact --complete-insert' ), null, array_merge($this->baseVariables($this->connection->getConfig()), [ // ])); $this->files->append($path, $process->getOutput()); } /** * Load the given schema file into the database. * * @param string $path * @return void */ public function load($path) { $command = 'mysql '.$this->connectionString().' --database="${:LARAVEL_LOAD_DATABASE}" < "${:LARAVEL_LOAD_PATH}"'; $process = $this->makeProcess($command)->setTimeout(null); $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ 'LARAVEL_LOAD_PATH' => $path, ])); } /** * Get the base dump command arguments for MySQL as a string. * * @return string */ protected function baseDumpCommand() { $command = 'mysqldump '.$this->connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; if (! $this->connection->isMaria()) { $command .= ' --set-gtid-purged=OFF'; } return $command.' "${:LARAVEL_LOAD_DATABASE}"'; } /** * Generate a basic connection string (--socket, --host, --port, --user, --password) for the database. * * @return string */ protected function connectionString() { $value = ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}"'; $config = $this->connection->getConfig(); $value .= $config['unix_socket'] ?? false ? ' --socket="${:LARAVEL_LOAD_SOCKET}"' : ' --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"'; if (isset($config['options'][\PDO::MYSQL_ATTR_SSL_CA])) { $value .= ' --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"'; } return $value; } /** * Get the base variables for a dump / load command. * * @param array $config * @return array */ protected function baseVariables(array $config) { $config['host'] ??= ''; return [ 'LARAVEL_LOAD_SOCKET' => $config['unix_socket'] ?? '', 'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'], 'LARAVEL_LOAD_PORT' => $config['port'] ?? '', 'LARAVEL_LOAD_USER' => $config['username'], 'LARAVEL_LOAD_PASSWORD' => $config['password'] ?? '', 'LARAVEL_LOAD_DATABASE' => $config['database'], 'LARAVEL_LOAD_SSL_CA' => $config['options'][\PDO::MYSQL_ATTR_SSL_CA] ?? '', ]; } /** * Execute the given dump process. * * @param \Symfony\Component\Process\Process $process * @param callable $output * @param array $variables * @return \Symfony\Component\Process\Process */ protected function executeDumpProcess(Process $process, $output, array $variables) { try { $process->setTimeout(null)->mustRun($output, $variables); } catch (Exception $e) { if (Str::contains($e->getMessage(), ['column-statistics', 'column_statistics'])) { return $this->executeDumpProcess(Process::fromShellCommandLine( str_replace(' --column-statistics=0', '', $process->getCommandLine()) ), $output, $variables); } if (str_contains($e->getMessage(), 'set-gtid-purged')) { return $this->executeDumpProcess(Process::fromShellCommandLine( str_replace(' --set-gtid-purged=OFF', '', $process->getCommandLine()) ), $output, $variables); } throw $e; } return $process; } } Schema/PostgresBuilder.php000075500000014352147536122140011607 0ustar00connection->statement( $this->grammar->compileCreateDatabase($name, $this->connection) ); } /** * Drop a database from the schema if the database exists. * * @param string $name * @return bool */ public function dropDatabaseIfExists($name) { return $this->connection->statement( $this->grammar->compileDropDatabaseIfExists($name) ); } /** * Determine if the given table exists. * * @param string $table * @return bool */ public function hasTable($table) { [$database, $schema, $table] = $this->parseSchemaAndTable($table); $table = $this->connection->getTablePrefix().$table; return count($this->connection->selectFromWriteConnection( $this->grammar->compileTableExists(), [$database, $schema, $table] )) > 0; } /** * Drop all tables from the database. * * @return void */ public function dropAllTables() { $tables = []; $excludedTables = $this->grammar->escapeNames( $this->connection->getConfig('dont_drop') ?? ['spatial_ref_sys'] ); foreach ($this->getAllTables() as $row) { $row = (array) $row; if (empty(array_intersect($this->grammar->escapeNames($row), $excludedTables))) { $tables[] = $row['qualifiedname'] ?? reset($row); } } if (empty($tables)) { return; } $this->connection->statement( $this->grammar->compileDropAllTables($tables) ); } /** * Drop all views from the database. * * @return void */ public function dropAllViews() { $views = []; foreach ($this->getAllViews() as $row) { $row = (array) $row; $views[] = $row['qualifiedname'] ?? reset($row); } if (empty($views)) { return; } $this->connection->statement( $this->grammar->compileDropAllViews($views) ); } /** * Drop all types from the database. * * @return void */ public function dropAllTypes() { $types = []; foreach ($this->getAllTypes() as $row) { $row = (array) $row; $types[] = reset($row); } if (empty($types)) { return; } $this->connection->statement( $this->grammar->compileDropAllTypes($types) ); } /** * Get all of the table names for the database. * * @return array */ public function getAllTables() { return $this->connection->select( $this->grammar->compileGetAllTables( $this->parseSearchPath( $this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') ) ) ); } /** * Get all of the view names for the database. * * @return array */ public function getAllViews() { return $this->connection->select( $this->grammar->compileGetAllViews( $this->parseSearchPath( $this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') ) ) ); } /** * Get all of the type names for the database. * * @return array */ public function getAllTypes() { return $this->connection->select( $this->grammar->compileGetAllTypes() ); } /** * Get the column listing for a given table. * * @param string $table * @return array */ public function getColumnListing($table) { [$database, $schema, $table] = $this->parseSchemaAndTable($table); $table = $this->connection->getTablePrefix().$table; $results = $this->connection->selectFromWriteConnection( $this->grammar->compileColumnListing(), [$database, $schema, $table] ); return $this->connection->getPostProcessor()->processColumnListing($results); } /** * Parse the database object reference and extract the database, schema, and table. * * @param string $reference * @return array */ protected function parseSchemaAndTable($reference) { $searchPath = $this->parseSearchPath( $this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') ?: 'public' ); $parts = explode('.', $reference); $database = $this->connection->getConfig('database'); // If the reference contains a database name, we will use that instead of the // default database name for the connection. This allows the database name // to be specified in the query instead of at the full connection level. if (count($parts) === 3) { $database = $parts[0]; array_shift($parts); } // We will use the default schema unless the schema has been specified in the // query. If the schema has been specified in the query then we can use it // instead of a default schema configured in the connection search path. $schema = $searchPath[0]; if (count($parts) === 2) { $schema = $parts[0]; array_shift($parts); } return [$database, $schema, $parts[0]]; } /** * Parse the "search_path" configuration value into an array. * * @param string|array|null $searchPath * @return array */ protected function parseSearchPath($searchPath) { return array_map(function ($schema) { return $schema === '$user' ? $this->connection->getConfig('username') : $schema; }, $this->baseParseSearchPath($searchPath)); } } Schema/PostgresSchemaState.php000064400000005104147536122140012412 0ustar00baseDumpCommand().' --schema-only > '.$path, $this->baseDumpCommand().' -t '.$this->migrationTable.' --data-only >> '.$path, ]); $commands->map(function ($command, $path) { $this->makeProcess($command)->mustRun($this->output, array_merge($this->baseVariables($this->connection->getConfig()), [ 'LARAVEL_LOAD_PATH' => $path, ])); }); } /** * Load the given schema file into the database. * * @param string $path * @return void */ public function load($path) { $command = 'pg_restore --no-owner --no-acl --clean --if-exists --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}" "${:LARAVEL_LOAD_PATH}"'; if (str_ends_with($path, '.sql')) { $command = 'psql --file="${:LARAVEL_LOAD_PATH}" --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}"'; } $process = $this->makeProcess($command); $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ 'LARAVEL_LOAD_PATH' => $path, ])); } /** * Get the base dump command arguments for PostgreSQL as a string. * * @return string */ protected function baseDumpCommand() { return 'pg_dump --no-owner --no-acl --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}"'; } /** * Get the base variables for a dump / load command. * * @param array $config * @return array */ protected function baseVariables(array $config) { $config['host'] ??= ''; return [ 'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'], 'LARAVEL_LOAD_PORT' => $config['port'] ?? '', 'LARAVEL_LOAD_USER' => $config['username'], 'PGPASSWORD' => $config['password'], 'LARAVEL_LOAD_DATABASE' => $config['database'], ]; } } Schema/SQLiteBuilder.php000064400000004434147536122140011137 0ustar00connection->getDatabaseName() !== ':memory:') { return $this->refreshDatabaseFile(); } $this->connection->select($this->grammar->compileEnableWriteableSchema()); $this->connection->select($this->grammar->compileDropAllTables()); $this->connection->select($this->grammar->compileDisableWriteableSchema()); $this->connection->select($this->grammar->compileRebuild()); } /** * Drop all views from the database. * * @return void */ public function dropAllViews() { $this->connection->select($this->grammar->compileEnableWriteableSchema()); $this->connection->select($this->grammar->compileDropAllViews()); $this->connection->select($this->grammar->compileDisableWriteableSchema()); $this->connection->select($this->grammar->compileRebuild()); } /** * Get all of the table names for the database. * * @return array */ public function getAllTables() { return $this->connection->select( $this->grammar->compileGetAllTables() ); } /** * Get all of the view names for the database. * * @return array */ public function getAllViews() { return $this->connection->select( $this->grammar->compileGetAllViews() ); } /** * Empty the database file. * * @return void */ public function refreshDatabaseFile() { file_put_contents($this->connection->getDatabaseName(), ''); } } Schema/SchemaState.php000064400000005275147536122140010674 0ustar00connection = $connection; $this->files = $files ?: new Filesystem; $this->processFactory = $processFactory ?: function (...$arguments) { return Process::fromShellCommandline(...$arguments)->setTimeout(null); }; $this->handleOutputUsing(function () { // }); } /** * Dump the database's schema into a file. * * @param \Illuminate\Database\Connection $connection * @param string $path * @return void */ abstract public function dump(Connection $connection, $path); /** * Load the given schema file into the database. * * @param string $path * @return void */ abstract public function load($path); /** * Create a new process instance. * * @param mixed ...$arguments * @return \Symfony\Component\Process\Process */ public function makeProcess(...$arguments) { return call_user_func($this->processFactory, ...$arguments); } /** * Specify the name of the application's migration table. * * @param string $table * @return $this */ public function withMigrationTable(string $table) { $this->migrationTable = $table; return $this; } /** * Specify the callback that should be used to handle process output. * * @param callable $output * @return $this */ public function handleOutputUsing(callable $output) { $this->output = $output; return $this; } } Schema/SqlServerBuilder.php000064400000003230147536122140011715 0ustar00connection->statement( $this->grammar->compileCreateDatabase($name, $this->connection) ); } /** * Drop a database from the schema if the database exists. * * @param string $name * @return bool */ public function dropDatabaseIfExists($name) { return $this->connection->statement( $this->grammar->compileDropDatabaseIfExists($name) ); } /** * Drop all tables from the database. * * @return void */ public function dropAllTables() { $this->connection->statement($this->grammar->compileDropAllForeignKeys()); $this->connection->statement($this->grammar->compileDropAllTables()); } /** * Drop all views from the database. * * @return void */ public function dropAllViews() { $this->connection->statement($this->grammar->compileDropAllViews()); } /** * Drop all tables from the database. * * @return array */ public function getAllTables() { return $this->connection->select( $this->grammar->compileGetAllTables() ); } /** * Get all of the view names for the database. * * @return array */ public function getAllViews() { return $this->connection->select( $this->grammar->compileGetAllViews() ); } } Schema/SqliteSchemaState.php000064400000005340147536122140012047 0ustar00makeProcess( $this->baseCommand().' .schema' ))->setTimeout(null)->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ // ])); $migrations = collect(preg_split("/\r\n|\n|\r/", $process->getOutput()))->filter(function ($line) { return stripos($line, 'sqlite_sequence') === false && strlen($line) > 0; })->all(); $this->files->put($path, implode(PHP_EOL, $migrations).PHP_EOL); $this->appendMigrationData($path); } /** * Append the migration data to the schema dump. * * @param string $path * @return void */ protected function appendMigrationData(string $path) { with($process = $this->makeProcess( $this->baseCommand().' ".dump \''.$this->migrationTable.'\'"' ))->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ // ])); $migrations = collect(preg_split("/\r\n|\n|\r/", $process->getOutput()))->filter(function ($line) { return preg_match('/^\s*(--|INSERT\s)/iu', $line) === 1 && strlen($line) > 0; })->all(); $this->files->append($path, implode(PHP_EOL, $migrations).PHP_EOL); } /** * Load the given schema file into the database. * * @param string $path * @return void */ public function load($path) { if ($this->connection->getDatabaseName() === ':memory:') { $this->connection->getPdo()->exec($this->files->get($path)); return; } $process = $this->makeProcess($this->baseCommand().' < "${:LARAVEL_LOAD_PATH}"'); $process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [ 'LARAVEL_LOAD_PATH' => $path, ])); } /** * Get the base sqlite command arguments as a string. * * @return string */ protected function baseCommand() { return 'sqlite3 "${:LARAVEL_LOAD_DATABASE}"'; } /** * Get the base variables for a dump / load command. * * @param array $config * @return array */ protected function baseVariables(array $config) { return [ 'LARAVEL_LOAD_DATABASE' => $config['database'], ]; } } Seeder.php000075500000011212147536122140006471 0ustar00resolve($class); $name = get_class($seeder); if ($silent === false && isset($this->command)) { with(new TwoColumnDetail($this->command->getOutput()))->render( $name, 'RUNNING' ); } $startTime = microtime(true); $seeder->__invoke($parameters); if ($silent === false && isset($this->command)) { $runTime = number_format((microtime(true) - $startTime) * 1000, 2); with(new TwoColumnDetail($this->command->getOutput()))->render( $name, "$runTime ms DONE" ); $this->command->getOutput()->writeln(''); } static::$called[] = $class; } return $this; } /** * Run the given seeder class. * * @param array|string $class * @param array $parameters * @return void */ public function callWith($class, array $parameters = []) { $this->call($class, false, $parameters); } /** * Silently run the given seeder class. * * @param array|string $class * @param array $parameters * @return void */ public function callSilent($class, array $parameters = []) { $this->call($class, true, $parameters); } /** * Run the given seeder class once. * * @param array|string $class * @param bool $silent * @return void */ public function callOnce($class, $silent = false, array $parameters = []) { if (in_array($class, static::$called)) { return; } $this->call($class, $silent, $parameters); } /** * Resolve an instance of the given seeder class. * * @param string $class * @return \Illuminate\Database\Seeder */ protected function resolve($class) { if (isset($this->container)) { $instance = $this->container->make($class); $instance->setContainer($this->container); } else { $instance = new $class; } if (isset($this->command)) { $instance->setCommand($this->command); } return $instance; } /** * Set the IoC container instance. * * @param \Illuminate\Contracts\Container\Container $container * @return $this */ public function setContainer(Container $container) { $this->container = $container; return $this; } /** * Set the console command instance. * * @param \Illuminate\Console\Command $command * @return $this */ public function setCommand(Command $command) { $this->command = $command; return $this; } /** * Run the database seeds. * * @param array $parameters * @return mixed * * @throws \InvalidArgumentException */ public function __invoke(array $parameters = []) { if (! method_exists($this, 'run')) { throw new InvalidArgumentException('Method [run] missing from '.get_class($this)); } $callback = fn () => isset($this->container) ? $this->container->call([$this, 'run'], $parameters) : $this->run(...$parameters); $uses = array_flip(class_uses_recursive(static::class)); if (isset($uses[WithoutModelEvents::class])) { $callback = $this->withoutModelEvents($callback); } return $callback(); } } SqlServerConnection.php000075500000010165147536122140011236 0ustar00getDriverName() === 'sqlsrv') { return parent::transaction($callback, $attempts); } $this->getPdo()->exec('BEGIN TRAN'); // We'll simply execute the given callback within a try / catch block // and if we catch any exception we can rollback the transaction // so that none of the changes are persisted to the database. try { $result = $callback($this); $this->getPdo()->exec('COMMIT TRAN'); } // If we catch an exception, we will rollback so nothing gets messed // up in the database. Then we'll re-throw the exception so it can // be handled how the developer sees fit for their applications. catch (Throwable $e) { $this->getPdo()->exec('ROLLBACK TRAN'); throw $e; } return $result; } } /** * Escape a binary value for safe SQL embedding. * * @param string $value * @return string */ protected function escapeBinary($value) { $hex = bin2hex($value); return "0x{$hex}"; } /** * Determine if the given database exception was caused by a unique constraint violation. * * @param \Exception $exception * @return bool */ protected function isUniqueConstraintError(Exception $exception) { return boolval(preg_match('#Cannot insert duplicate key row in object#i', $exception->getMessage())); } /** * Get the default query grammar instance. * * @return \Illuminate\Database\Query\Grammars\SqlServerGrammar */ protected function getDefaultQueryGrammar() { ($grammar = new QueryGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * Get a schema builder instance for the connection. * * @return \Illuminate\Database\Schema\SqlServerBuilder */ public function getSchemaBuilder() { if (is_null($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } return new SqlServerBuilder($this); } /** * Get the default schema grammar instance. * * @return \Illuminate\Database\Schema\Grammars\SqlServerGrammar */ protected function getDefaultSchemaGrammar() { ($grammar = new SchemaGrammar)->setConnection($this); return $this->withTablePrefix($grammar); } /** * Get the schema state for the connection. * * @param \Illuminate\Filesystem\Filesystem|null $files * @param callable|null $processFactory * * @throws \RuntimeException */ public function getSchemaState(Filesystem $files = null, callable $processFactory = null) { throw new RuntimeException('Schema dumping is not supported when using SQL Server.'); } /** * Get the default post processor instance. * * @return \Illuminate\Database\Query\Processors\SqlServerProcessor */ protected function getDefaultPostProcessor() { return new SqlServerProcessor; } /** * Get the Doctrine DBAL driver. * * @return \Illuminate\Database\PDO\SqlServerDriver */ protected function getDoctrineDriver() { return new SqlServerDriver; } } UniqueConstraintViolationException.php000064400000000153147536122140014340 0ustar00