INIT
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\MySqlConnection;
|
||||
use Illuminate\Database\PostgresConnection;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Composer;
|
||||
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
abstract class DatabaseInspectionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* A map of database column types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $typeMappings = [
|
||||
'bit' => '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 $e) {
|
||||
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 && $this->components->confirm('Inspecting database information requires the Doctrine DBAL (doctrine/dbal) package. Would you like to install it?')) {
|
||||
$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')
|
||||
->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\ConfigurationUrlParser;
|
||||
use Symfony\Component\Process\Process;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class DbCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db {connection? : The database connection that should be used}
|
||||
{--read : Connect to the read connection}
|
||||
{--write : Connect to the write connection}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Start a new database CLI session';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
|
||||
if (! isset($connection['host']) && $connection['driver'] !== 'sqlite') {
|
||||
$this->components->error('No host specified for this database connection.');
|
||||
$this->line(' Use the <options=bold>[--read]</> and <options=bold>[--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));
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Database\Events\SchemaDumped;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'schema:dump')]
|
||||
class DumpCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'schema:dump
|
||||
{--database= : The database connection to use}
|
||||
{--path= : The path where the schema dump file should be stored}
|
||||
{--prune : Delete all existing migration files}';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'schema:dump';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dump the given database schema';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $connections
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ConnectionResolverInterface $connections, Dispatcher $dispatcher)
|
||||
{
|
||||
$connection = $connections->connection($database = $this->input->getOption('database'));
|
||||
|
||||
$this->schemaState($connection)->dump(
|
||||
$connection, $path = $this->path($connection)
|
||||
);
|
||||
|
||||
$dispatcher->dispatch(new SchemaDumped($connection, $path));
|
||||
|
||||
$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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Factories;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'make:factory')]
|
||||
class FactoryMakeCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:factory';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'make:factory';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new model factory';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Factory';
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/factory.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.$stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name)));
|
||||
|
||||
$namespaceModel = $this->option('model')
|
||||
? $this->qualifyModel($this->option('model'))
|
||||
: $this->qualifyModel($this->guessModelName($name));
|
||||
|
||||
$model = class_basename($namespaceModel);
|
||||
|
||||
$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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace {{ factoryNamespace }};
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\{{ namespacedModel }}>
|
||||
*/
|
||||
class {{ factory }}Factory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class BaseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Get all of the migration paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getMigrationPaths()
|
||||
{
|
||||
// Here, we will check to see if a path option has been defined. If it has we will
|
||||
// use the path relative to the root of the installation folder so our database
|
||||
// migrations may be run for any customized path from within the application.
|
||||
if ($this->input->hasOption('path') && $this->option('path')) {
|
||||
return collect($this->option('path'))->map(function ($path) {
|
||||
return ! $this->usingRealPath()
|
||||
? $this->laravel->basePath().'/'.$path
|
||||
: $path;
|
||||
})->all();
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$this->migrator->paths(), [$this->getMigrationPath()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given path(s) are pre-resolved "real" paths.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function usingRealPath()
|
||||
{
|
||||
return $this->input->hasOption('realpath') && $this->option('realpath');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the migration directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getMigrationPath()
|
||||
{
|
||||
return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'migrations';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\DatabaseRefreshed;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class FreshCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:fresh';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables and re-run all migrations';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
$this->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
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->needsSeeding()) {
|
||||
$this->runSeeder($database);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the developer has requested database seeding.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function needsSeeding()
|
||||
{
|
||||
return $this->option('seed') || $this->option('seeder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeder command.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function runSeeder($database)
|
||||
{
|
||||
$this->call('db:seed', array_filter([
|
||||
'--database' => $database,
|
||||
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['drop-views', null, InputOption::VALUE_NONE, 'Drop all tables and views'],
|
||||
['drop-types', null, InputOption::VALUE_NONE, 'Drop all tables and types (Postgres only)'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
['schema-path', null, InputOption::VALUE_OPTIONAL, 'The path to a schema dump file'],
|
||||
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'],
|
||||
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'],
|
||||
['step', null, InputOption::VALUE_NONE, 'Force the migrations to be run so they can be rolled back individually'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:install';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create the migration repository';
|
||||
|
||||
/**
|
||||
* The repository instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* Create a new migration install command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(MigrationRepositoryInterface $repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->repository->setSource($this->input->getOption('database'));
|
||||
|
||||
$this->repository->createRepository();
|
||||
|
||||
$this->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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Console\Isolatable;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\SchemaLoaded;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Database\SQLiteDatabaseDoesNotExistException;
|
||||
use Illuminate\Database\SqlServerConnection;
|
||||
use PDOException;
|
||||
use Throwable;
|
||||
|
||||
class MigrateCommand extends BaseCommand implements Isolatable
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'migrate {--database= : The database connection to use}
|
||||
{--force : Force the operation to run when in production}
|
||||
{--path=* : The path(s) to the migrations files to be executed}
|
||||
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
|
||||
{--schema-path= : The path to a schema dump file}
|
||||
{--pretend : Dump the SQL queries that would be run}
|
||||
{--seed : Indicates if the seed task should be re-run}
|
||||
{--seeder= : The class name of the root seeder}
|
||||
{--step : Force the migrations to be run so they can be rolled back individually}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run the database migrations';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* The event dispatcher instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* Create a new migration command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator, Dispatcher $dispatcher)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->migrator->usingConnection($this->option('database'), function () {
|
||||
$this->prepareDatabase();
|
||||
|
||||
// Next, we will check to see if a path option has been defined. If it has
|
||||
// we will use the path relative to the root of this installation folder
|
||||
// so that migrations may be run for any path within the applications.
|
||||
$migrations = $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->createMissingSqliteDatbase($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 createMissingSqliteDatbase($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 (! $this->components->confirm('Would you like to create it?')) {
|
||||
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 (! $this->components->confirm('Would you like to create it?')) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||
use Illuminate\Database\Migrations\MigrationCreator;
|
||||
use Illuminate\Support\Composer;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MigrateMakeCommand extends BaseCommand implements PromptsForMissingInput
|
||||
{
|
||||
/**
|
||||
* The console command signature.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'make:migration {name : The name of the migration}
|
||||
{--create= : The table to be created}
|
||||
{--table= : The table to migrate}
|
||||
{--path= : The location where the migration file should be created}
|
||||
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
|
||||
{--fullpath : Output the full path of the migration (Deprecated)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new migration file';
|
||||
|
||||
/**
|
||||
* The migration creator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\MigrationCreator
|
||||
*/
|
||||
protected $creator;
|
||||
|
||||
/**
|
||||
* The Composer instance.
|
||||
*
|
||||
* @var \Illuminate\Support\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Create a new migration install command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\MigrationCreator $creator
|
||||
* @param \Illuminate\Support\Composer $composer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(MigrationCreator $creator, Composer $composer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->creator = $creator;
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// It's possible for the developer to specify the tables to modify in this
|
||||
// schema operation. The developer may also specify if this table needs
|
||||
// to be freshly created so we can create the appropriate migrations.
|
||||
$name = Str::snake(trim($this->input->getArgument('name')));
|
||||
|
||||
$table = $this->input->getOption('table');
|
||||
|
||||
$create = $this->input->getOption('create') ?: false;
|
||||
|
||||
// If no table was given as an option but a create option is given then we
|
||||
// will use the "create" option as the table name. This allows the devs
|
||||
// to pass a table name into this option as a short-cut for creating.
|
||||
if (! $table && is_string($create)) {
|
||||
$table = $create;
|
||||
|
||||
$create = true;
|
||||
}
|
||||
|
||||
// Next, we will attempt to guess the table name if this the migration has
|
||||
// "create" in the name. This will allow us to provide a convenient way
|
||||
// of creating migrations that create new tables for the application.
|
||||
if (! $table) {
|
||||
[$table, $create] = TableGuesser::guess($name);
|
||||
}
|
||||
|
||||
// Now we are ready to write the migration out to disk. Once we've written
|
||||
// the migration out, we will dump-autoload for the entire framework to
|
||||
// make sure that the migrations are registered by the class loaders.
|
||||
$this->writeMigration($name, $table, $create);
|
||||
|
||||
$this->composer->dumpAutoloads();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration file to disk.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $table
|
||||
* @param bool $create
|
||||
* @return string
|
||||
*/
|
||||
protected function writeMigration($name, $table, $create)
|
||||
{
|
||||
$file = $this->creator->create(
|
||||
$name, $this->getMigrationPath(), $table, $create
|
||||
);
|
||||
|
||||
$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?',
|
||||
];
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Events\DatabaseRefreshed;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class RefreshCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:refresh';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Reset and re-run all migrations';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Next we'll gather some of the options so that we can have the right options
|
||||
// to pass to the commands. This includes options such as which database to
|
||||
// use and the path to use for the migration. Then we'll run the command.
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
$path = $this->input->getOption('path');
|
||||
|
||||
// If the "step" option is specified it means we only want to rollback a small
|
||||
// number of migrations before migrating again. For example, the user might
|
||||
// only rollback and remigrate the latest four migrations instead of all.
|
||||
$step = $this->input->getOption('step') ?: 0;
|
||||
|
||||
if ($step > 0) {
|
||||
$this->runRollback($database, $path, $step);
|
||||
} else {
|
||||
$this->runReset($database, $path);
|
||||
}
|
||||
|
||||
// The refresh command is essentially just a brief aggregate of a few other of
|
||||
// the migration commands and just provides a convenient wrapper to execute
|
||||
// them in succession. We'll also see if we need to re-seed the database.
|
||||
$this->call('migrate', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
if ($this->laravel->bound(Dispatcher::class)) {
|
||||
$this->laravel[Dispatcher::class]->dispatch(
|
||||
new DatabaseRefreshed
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->needsSeeding()) {
|
||||
$this->runSeeder($database);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the rollback command.
|
||||
*
|
||||
* @param string $database
|
||||
* @param string $path
|
||||
* @param int $step
|
||||
* @return void
|
||||
*/
|
||||
protected function runRollback($database, $path, $step)
|
||||
{
|
||||
$this->call('migrate:rollback', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--step' => $step,
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the reset command.
|
||||
*
|
||||
* @param string $database
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function runReset($database, $path)
|
||||
{
|
||||
$this->call('migrate:reset', array_filter([
|
||||
'--database' => $database,
|
||||
'--path' => $path,
|
||||
'--realpath' => $this->input->getOption('realpath'),
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the developer has requested database seeding.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function needsSeeding()
|
||||
{
|
||||
return $this->option('seed') || $this->option('seeder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the database seeder command.
|
||||
*
|
||||
* @param string $database
|
||||
* @return void
|
||||
*/
|
||||
protected function runSeeder($database)
|
||||
{
|
||||
$this->call('db:seed', array_filter([
|
||||
'--database' => $database,
|
||||
'--class' => $this->option('seeder') ?: 'Database\\Seeders\\DatabaseSeeder',
|
||||
'--force' => true,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
['seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run'],
|
||||
['seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder'],
|
||||
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted & re-run'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class ResetCommand extends BaseCommand
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:reset';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rollback all database migrations';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return $this->migrator->usingConnection($this->option('database'), function () {
|
||||
// First, we'll make sure that the migration table actually exists before we
|
||||
// start trying to rollback and re-run all of the migrations. If it's not
|
||||
// present we'll just bail out with an info message for the developers.
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
return $this->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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class RollbackCommand extends BaseCommand
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:rollback';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rollback the last database migration';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->migrator->usingConnection($this->option('database'), function () {
|
||||
$this->migrator->setOutput($this->output)->rollback(
|
||||
$this->getMigrationPaths(), [
|
||||
'pretend' => $this->option('pretend'),
|
||||
'step' => (int) $this->option('step'),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use'],
|
||||
|
||||
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
|
||||
|
||||
['path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The path(s) to the migrations files to be executed'],
|
||||
|
||||
['realpath', null, InputOption::VALUE_NONE, 'Indicate any provided migration file paths are pre-resolved absolute paths'],
|
||||
|
||||
['pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run'],
|
||||
|
||||
['step', null, InputOption::VALUE_OPTIONAL, 'The number of migrations to be reverted'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class StatusCommand extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'migrate:status';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Show the status of each migration';
|
||||
|
||||
/**
|
||||
* The migrator instance.
|
||||
*
|
||||
* @var \Illuminate\Database\Migrations\Migrator
|
||||
*/
|
||||
protected $migrator;
|
||||
|
||||
/**
|
||||
* Create a new migration rollback command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\Migrations\Migrator $migrator
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Migrator $migrator)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->migrator = $migrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
return $this->migrator->usingConnection($this->option('database'), function () {
|
||||
if (! $this->migrator->repositoryExists()) {
|
||||
$this->components->error('Migration table not found.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$ran = $this->migrator->getRepository()->getRan();
|
||||
|
||||
$batches = $this->migrator->getRepository()->getMigrationBatches();
|
||||
|
||||
if (count($migrations = $this->getStatusFor($ran, $batches)) > 0) {
|
||||
$this->newLine();
|
||||
|
||||
$this->components->twoColumnDetail('<fg=gray>Migration name</>', '<fg=gray>Batch / Status</>');
|
||||
|
||||
$migrations
|
||||
->when($this->option('pending'), fn ($collection) => $collection->filter(function ($migration) {
|
||||
return str($migration[1])->contains('Pending');
|
||||
}))
|
||||
->each(
|
||||
fn ($migration) => $this->components->twoColumnDetail($migration[0], $migration[1])
|
||||
);
|
||||
|
||||
$this->newLine();
|
||||
} 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)
|
||||
? '<fg=green;options=bold>Ran</>'
|
||||
: '<fg=yellow;options=bold>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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Migrations;
|
||||
|
||||
class TableGuesser
|
||||
{
|
||||
const CREATE_PATTERNS = [
|
||||
'/^create_(\w+)_table$/',
|
||||
'/^create_(\w+)$/',
|
||||
];
|
||||
|
||||
const CHANGE_PATTERNS = [
|
||||
'/_(to|from|in)_(\w+)_table$/',
|
||||
'/_(to|from|in)_(\w+)$/',
|
||||
];
|
||||
|
||||
/**
|
||||
* Attempt to guess the table name and "creation" status of the given migration.
|
||||
*
|
||||
* @param string $migration
|
||||
* @return array
|
||||
*/
|
||||
public static function guess($migration)
|
||||
{
|
||||
foreach (self::CREATE_PATTERNS as $pattern) {
|
||||
if (preg_match($pattern, $migration, $matches)) {
|
||||
return [$matches[1], $create = true];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::CHANGE_PATTERNS as $pattern) {
|
||||
if (preg_match($pattern, $migration, $matches)) {
|
||||
return [$matches[2], $create = false];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Database\Events\DatabaseBusy;
|
||||
use Illuminate\Support\Composer;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'db:monitor')]
|
||||
class MonitorCommand extends DatabaseInspectionCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:monitor
|
||||
{--databases= : The database connections to monitor}
|
||||
{--max= : The maximum number of connections that can be open before an event is dispatched}';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'db:monitor';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Monitor the number of connections on the specified database';
|
||||
|
||||
/**
|
||||
* The connection resolver instance.
|
||||
*
|
||||
* @var \Illuminate\Database\ConnectionResolverInterface
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The events dispatcher instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $connection
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @param \Illuminate\Support\Composer $composer
|
||||
*/
|
||||
public function __construct(ConnectionResolverInterface $connection, Dispatcher $events, Composer $composer)
|
||||
{
|
||||
parent::__construct($composer);
|
||||
|
||||
$this->connection = $connection;
|
||||
$this->events = $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$databases = $this->parseDatabases($this->option('databases'));
|
||||
|
||||
$this->displayConnections($databases);
|
||||
|
||||
if ($this->option('max')) {
|
||||
$this->dispatchEvents($databases);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the database into an array of the connections.
|
||||
*
|
||||
* @param string $databases
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function parseDatabases($databases)
|
||||
{
|
||||
return 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 ? '<fg=yellow;options=bold>ALERT</>' : '<fg=green;options=bold>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('<fg=gray>Database name</>', '<fg=gray>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'] === '<fg=green;options=bold>OK</>') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->events->dispatch(
|
||||
new DatabaseBusy(
|
||||
$database['database'],
|
||||
$database['connections']
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\MassPrunable;
|
||||
use Illuminate\Database\Eloquent\Prunable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Events\ModelsPruned;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class PruneCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'model:prune
|
||||
{--model=* : Class names of the models to be pruned}
|
||||
{--except=* : Class names of the models to be excluded from pruning}
|
||||
{--chunk=1000 : The number of models to retrieve per chunk of models to be deleted}
|
||||
{--pretend : Display the number of prunable records found instead of deleting them}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Prune models that are no longer needed';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Dispatcher $events)
|
||||
{
|
||||
$models = $this->models();
|
||||
|
||||
if ($models->isEmpty()) {
|
||||
$this->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");
|
||||
});
|
||||
|
||||
$models->each(function ($model) {
|
||||
$this->pruneModel($model);
|
||||
});
|
||||
|
||||
$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 $this->isPrunable($model);
|
||||
})->filter(function ($model) {
|
||||
return class_exists($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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Illuminate\Database\ConnectionResolverInterface as Resolver;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'db:seed')]
|
||||
class SeedCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:seed';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'db:seed';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Seed the database with records';
|
||||
|
||||
/**
|
||||
* The connection resolver instance.
|
||||
*
|
||||
* @var \Illuminate\Database\ConnectionResolverInterface
|
||||
*/
|
||||
protected $resolver;
|
||||
|
||||
/**
|
||||
* Create a new database seed command instance.
|
||||
*
|
||||
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Resolver $resolver)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'make:seeder')]
|
||||
class SeederMakeCommand extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'make:seeder';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'make:seeder';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new seeder class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Seeder';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
parent::handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/seeder.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.$stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination class path.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getPath($name)
|
||||
{
|
||||
$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\\';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console\Seeds;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
trait WithoutModelEvents
|
||||
{
|
||||
/**
|
||||
* Prevent model events from being dispatched by the given callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return callable
|
||||
*/
|
||||
public function withoutModelEvents(callable $callback)
|
||||
{
|
||||
return fn () => Model::withoutEvents($callback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class {{ class }} extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\View;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Support\Arr;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'db:show')]
|
||||
class ShowCommand extends DatabaseInspectionCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:show {--database= : The database connection}
|
||||
{--json : Output the database information as JSON}
|
||||
{--counts : Show the table row count <bg=red;options=bold> Note: This can be slow on large databases </>};
|
||||
{--views : Show the database views <bg=red;options=bold> 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'));
|
||||
|
||||
$schema = $connection->getDoctrineSchemaManager();
|
||||
|
||||
$this->registerTypeMappings($schema->getDatabasePlatform());
|
||||
|
||||
$data = [
|
||||
'platform' => [
|
||||
'config' => $this->getConfigFromDatabase($database),
|
||||
'name' => $this->getPlatformName($schema->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('<fg=green;options=bold>'.$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('<fg=green;options=bold>Table</>', 'Size (MiB)'.($this->option('counts') ? ' <fg=gray;options=bold>/</> <fg=yellow;options=bold>Rows</>' : ''));
|
||||
|
||||
$tables->each(function ($table) {
|
||||
if ($tableSize = $table['size']) {
|
||||
$tableSize = number_format($tableSize / 1024 / 1024, 2);
|
||||
}
|
||||
|
||||
$this->components->twoColumnDetail(
|
||||
$table['table'].($this->output->isVerbose() ? ' <fg=gray>'.$table['engine'].'</>' : null),
|
||||
($tableSize ? $tableSize : '—').($this->option('counts') ? ' <fg=gray;options=bold>/</> <fg=yellow;options=bold>'.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('<fg=green;options=bold>View</>', '<fg=green;options=bold>Rows</>');
|
||||
|
||||
$views->each(fn ($view) => $this->components->twoColumnDetail($view['view'], number_format($view['rows'])));
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Illuminate\Database\ConnectionResolverInterface;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'db:table')]
|
||||
class TableCommand extends DatabaseInspectionCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'db:table
|
||||
{table? : The name of the table}
|
||||
{--database= : The database connection}
|
||||
{--json : Output the table information as JSON}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Display information about the given database table';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(ConnectionResolverInterface $connections)
|
||||
{
|
||||
if (! $this->ensureDependenciesExist()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$connection = $connections->connection($this->input->getOption('database'));
|
||||
|
||||
$schema = $connection->getDoctrineSchemaManager();
|
||||
|
||||
$this->registerTypeMappings($schema->getDatabasePlatform());
|
||||
|
||||
$table = $this->argument('table') ?: $this->components->choice(
|
||||
'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->listTableDetails($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('<fg=green;options=bold>'.$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('<fg=green;options=bold>Column</>', 'Type');
|
||||
|
||||
$columns->each(function ($column) {
|
||||
$this->components->twoColumnDetail(
|
||||
$column['column'].' <fg=gray>'.$column['attributes']->implode(', ').'</>',
|
||||
($column['default'] ? '<fg=gray>'.$column['default'].'</> ' : '').''.$column['type'].''
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($indexes->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>Index</>');
|
||||
|
||||
$indexes->each(function ($index) {
|
||||
$this->components->twoColumnDetail(
|
||||
$index['name'].' <fg=gray>'.$index['columns']->implode(', ').'</>',
|
||||
$index['attributes']->implode(', ')
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($foreignKeys->isNotEmpty()) {
|
||||
$this->components->twoColumnDetail('<fg=green;options=bold>Foreign Key</>', 'On Update / On Delete');
|
||||
|
||||
$foreignKeys->each(function ($foreignKey) {
|
||||
$this->components->twoColumnDetail(
|
||||
$foreignKey['name'].' <fg=gray;options=bold>'.$foreignKey['local_columns']->implode(', ').' references '.$foreignKey['foreign_columns']->implode(', ').' on '.$foreignKey['foreign_table'].'</>',
|
||||
$foreignKey['on_update'].' / '.$foreignKey['on_delete'],
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Console\ConfirmableTrait;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'db:wipe')]
|
||||
class WipeCommand extends Command
|
||||
{
|
||||
use ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'db:wipe';
|
||||
|
||||
/**
|
||||
* The name of the console command.
|
||||
*
|
||||
* This name is used to identify the command during lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
protected static $defaultName = 'db:wipe';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables, views, and types';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->confirmToProceed()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$database = $this->input->getOption('database');
|
||||
|
||||
if ($this->option('drop-views')) {
|
||||
$this->dropAllViews($database);
|
||||
|
||||
$this->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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user