Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 45 additions & 64 deletions system/Commands/Database/CreateDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,75 +13,60 @@

namespace CodeIgniter\Commands\Database;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\AbstractCommand;
use CodeIgniter\CLI\Attributes\Command;
use CodeIgniter\CLI\CLI;
use CodeIgniter\CLI\Input\Argument;
use CodeIgniter\CLI\Input\Option;
use CodeIgniter\Config\Factories;
use CodeIgniter\Database\SQLite3\Connection;
use CodeIgniter\Database\SQLite3\Connection as SQLite3Connection;
use Config\Database;
use Throwable;

/**
* Creates a new database.
*/
class CreateDatabase extends BaseCommand
#[Command(name: 'db:create', description: 'Create a new database schema.', group: 'Database')]
class CreateDatabase extends AbstractCommand
{
/**
* The group the command is lumped under
* when listing commands.
*
* @var string
* @var list<string>
*/
protected $group = 'Database';
private const VALID_EXTENSIONS = ['db', 'sqlite'];

/**
* The Command's name
*
* @var string
*/
protected $name = 'db:create';
protected function configure(): void
{
$this
->addArgument(new Argument(
name: 'db_name',
description: 'The database name to use.',
required: true,
))
->addOption(new Option(
name: 'ext',
description: 'File extension of the database file for SQLite3. Can be `db` or `sqlite`.',
requiresValue: true,
default: 'db',
));
}

/**
* the Command's short description
*
* @var string
*/
protected $description = 'Create a new database schema.';
protected function interact(array &$arguments, array &$options): void
{
if ($arguments === []) {
$arguments[] = CLI::prompt('Database name', null, 'required');
}

/**
* the Command's usage
*
* @var string
*/
protected $usage = 'db:create <db_name> [options]';
$ext = $this->getUnboundOption('ext', $options);

/**
* The Command's arguments
*
* @var array<string, string>
*/
protected $arguments = [
'db_name' => 'The database name to use',
];

/**
* The Command's options
*
* @var array<string, string>
*/
protected $options = [
'--ext' => 'File extension of the database file for SQLite3. Can be `db` or `sqlite`. Defaults to `db`.',
];
if (is_string($ext) && ! in_array($ext, self::VALID_EXTENSIONS, true)) {
$options['ext'] = CLI::prompt('Please choose a valid file extension', self::VALID_EXTENSIONS, 'required');
}
}

/**
* Creates a new database.
*/
public function run(array $params)
protected function execute(array $arguments, array $options): int
{
$name = array_shift($params);

if (empty($name)) {
$name = CLI::prompt('Database name', null, 'required'); // @codeCoverageIgnore
}
$name = $arguments['db_name'];
assert(is_string($name));

try {
$config = config(Database::class);
Expand All @@ -93,12 +78,14 @@ public function run(array $params)

$db = Database::connect();

// Special SQLite3 handling
if ($db instanceof Connection) {
$ext = $params['ext'] ?? CLI::getOption('ext') ?? 'db';
if ($db instanceof SQLite3Connection) {
$ext = $options['ext'];
assert(is_string($ext));

if (! in_array($ext, ['db', 'sqlite'], true)) {
$ext = CLI::prompt('Please choose a valid file extension', ['db', 'sqlite']); // @codeCoverageIgnore
if (! in_array($ext, self::VALID_EXTENSIONS, true)) {
CLI::error(sprintf('Invalid file extension "%s". Use either `db` or `sqlite`.', $ext), 'light_gray', 'red');

return EXIT_ERROR;
}

if ($name !== ':memory:') {
Expand All @@ -113,7 +100,6 @@ public function run(array $params)

if (is_file($dbName)) {
CLI::error("Database \"{$dbName}\" already exists.", 'light_gray', 'red');
CLI::newLine();

return EXIT_ERROR;
}
Expand All @@ -128,26 +114,21 @@ public function run(array $params)
if (! is_file($db->getDatabase()) && $name !== ':memory:') {
// @codeCoverageIgnoreStart
CLI::error('Database creation failed.', 'light_gray', 'red');
CLI::newLine();

return EXIT_ERROR;
// @codeCoverageIgnoreEnd
}
} elseif (! Database::forge()->createDatabase($name)) {
// @codeCoverageIgnoreStart
CLI::error('Database creation failed.', 'light_gray', 'red');
CLI::newLine();

return EXIT_ERROR;
// @codeCoverageIgnoreEnd
}

CLI::write("Database \"{$name}\" successfully created.", 'green');
CLI::newLine();

return EXIT_SUCCESS;
} catch (Throwable $e) {
$this->showError($e);
$this->renderThrowable($e);

return EXIT_ERROR;
} finally {
Expand Down
96 changes: 91 additions & 5 deletions tests/system/Commands/CreateDatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

namespace CodeIgniter\Commands;

use CodeIgniter\CLI\CLI;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\OCI8\Connection as OCI8Connection;
use CodeIgniter\Database\SQLite3\Connection as SQLite3Connection;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockInputOutput;
use CodeIgniter\Test\StreamFilterTrait;
use Config\Database;
use PHPUnit\Framework\Attributes\Group;
Expand All @@ -39,13 +41,15 @@ protected function setUp(): void

parent::setUp();

CLI::resetLastWrite();
$this->dropDatabase();
}

protected function tearDown(): void
{
parent::tearDown();

CLI::reset();
$this->dropDatabase();
}

Expand Down Expand Up @@ -81,9 +85,9 @@ private function closeDatabaseConnections(): void
$this->setPrivateProperty(Database::class, 'instances', []);
}

protected function getBuffer(): string
private function getUndecoratedBuffer(): string
{
return $this->getStreamFilterBuffer();
return preg_replace('/\e\[[^m]+m/', '', $this->getStreamFilterBuffer()) ?? '';
}

public function testCreateDatabase(): void
Expand All @@ -92,8 +96,17 @@ public function testCreateDatabase(): void
$this->markTestSkipped('Needs to run on non-OCI8 drivers.');
}

$name = $this->connection instanceof SQLite3Connection ? 'database.db' : 'database';

command('db:create database');
$this->assertStringContainsString('successfully created.', $this->getBuffer());
$this->assertSame(
<<<EOT

Database "{$name}" successfully created.

EOT,
$this->getUndecoratedBuffer(),
);
}

public function testSqliteDatabaseDuplicated(): void
Expand All @@ -104,9 +117,19 @@ public function testSqliteDatabaseDuplicated(): void

command('db:create database');
$this->resetStreamFilterBuffer();
CLI::resetLastWrite();

$database = WRITEPATH . 'database.db';

command('db:create database --ext db');
$this->assertStringContainsString('already exists.', $this->getBuffer());
$this->assertSame(
<<<EOT

Database "{$database}" already exists.

EOT,
$this->getUndecoratedBuffer(),
);
}

public function testOtherDriverDuplicatedDatabase(): void
Expand All @@ -119,6 +142,69 @@ public function testOtherDriverDuplicatedDatabase(): void
$this->resetStreamFilterBuffer();

command('db:create database');
$this->assertStringContainsString('Unable to create the specified database.', $this->getBuffer());
$this->assertStringContainsString('Unable to create the specified database.', $this->getUndecoratedBuffer());
}

public function testOtherDriverCreationFailsWithoutDebug(): void
{
if ($this->connection instanceof SQLite3Connection || $this->connection instanceof OCI8Connection) {
$this->markTestSkipped('Needs to run on non-SQLite3 and non-OCI8 drivers.');
}

command('db:create database');
$this->resetStreamFilterBuffer();

$this->connection = Database::connect();
$this->setPrivateProperty($this->connection, 'DBDebug', false);

CLI::resetLastWrite();
command('db:create database');
$this->assertSame(
<<<'EOT'

Database creation failed.

EOT,
$this->getUndecoratedBuffer(),
);
}

public function testSqlitePromptsForInvalidExtension(): void
{
if (! $this->connection instanceof SQLite3Connection) {
$this->markTestSkipped('Needs to run on SQLite3.');
}

$io = new MockInputOutput();
$io->setInputs(['db']);
CLI::setInputOutput($io);

command('db:create database --ext txt');

$this->assertSame(
<<<'EOT'
Please choose a valid file extension [db, sqlite]: db
Database "database.db" successfully created.

EOT,
preg_replace('/\e\[[^m]+m/', '', $io->getOutput()) ?? '',
);
}

public function testSqliteRejectsInvalidExtensionWhenNonInteractive(): void
{
if (! $this->connection instanceof SQLite3Connection) {
$this->markTestSkipped('Needs to run on SQLite3.');
}

command('db:create database --ext txt --no-interaction');
$this->assertSame(
<<<'EOT'

Invalid file extension "txt". Use either `db` or `sqlite`.

EOT,
$this->getUndecoratedBuffer(),
);
}
}
7 changes: 1 addition & 6 deletions utils/phpstan-baseline/empty.notAllowed.neon
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
# total 210 errors
# total 209 errors

parameters:
ignoreErrors:
-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
count: 1
path: ../../system/Commands/Database/CreateDatabase.php

-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
count: 1
Expand Down
Loading