mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-07 15:53:13 +08:00
99 lines
3.6 KiB
PHP
99 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Shlinkio\Shlink\CLI\Command\Db;
|
|
|
|
use Doctrine\DBAL\Connection;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
|
use Shlinkio\Shlink\CLI\Command\Util\CommandUtils;
|
|
use Shlinkio\Shlink\CLI\Command\Util\LockConfig;
|
|
use Shlinkio\Shlink\CLI\Util\ProcessRunnerInterface;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
use Symfony\Component\Lock\LockFactory;
|
|
use Throwable;
|
|
|
|
use function array_map;
|
|
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
|
use function Shlinkio\Shlink\Core\ArrayUtils\some;
|
|
|
|
#[AsCommand(
|
|
name: CreateDatabaseCommand::NAME,
|
|
description: 'Creates the database needed for shlink to work. It will do nothing if the database already exists',
|
|
hidden: true,
|
|
)]
|
|
class CreateDatabaseCommand extends Command
|
|
{
|
|
private readonly Connection $regularConn;
|
|
|
|
public const string NAME = 'db:create';
|
|
public const string SCRIPT = 'bin/doctrine';
|
|
public const string COMMAND = 'orm:schema-tool:create';
|
|
|
|
public function __construct(
|
|
private readonly LockFactory $locker,
|
|
private readonly ProcessRunnerInterface $processRunner,
|
|
private readonly EntityManagerInterface $em,
|
|
private readonly Connection $noDbNameConn,
|
|
) {
|
|
$this->regularConn = $this->em->getConnection();
|
|
parent::__construct();
|
|
}
|
|
|
|
public function __invoke(SymfonyStyle $io): int
|
|
{
|
|
return CommandUtils::executeWithLock(
|
|
$this->locker,
|
|
LockConfig::blocking(self::NAME),
|
|
$io,
|
|
fn () => $this->executeCommand($io),
|
|
);
|
|
}
|
|
|
|
private function executeCommand(SymfonyStyle $io): int
|
|
{
|
|
if ($this->databaseTablesExist()) {
|
|
$io->success('Database already exists. Run "db:migrate" command to make sure it is up to date.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
// Create database
|
|
$io->writeln('<fg=blue>Creating database tables...</>');
|
|
$this->processRunner->run($io, [self::SCRIPT, self::COMMAND, '--no-interaction']);
|
|
$io->success('Database properly created!');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function databaseTablesExist(): bool
|
|
{
|
|
$existingTables = $this->ensureDatabaseExistsAndGetTables();
|
|
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
|
|
$shlinkTables = array_map(static fn (ClassMetadata $metadata) => $metadata->getTableName(), $allMetadata);
|
|
|
|
// If at least one of the shlink tables exist, we will consider the database exists somehow.
|
|
// Any other inconsistency will be taken care of by the migrations.
|
|
return some($shlinkTables, static fn (string $shlinkTable) => contains($shlinkTable, $existingTables));
|
|
}
|
|
|
|
private function ensureDatabaseExistsAndGetTables(): array
|
|
{
|
|
try {
|
|
// Trying to list tables requires opening a connection to configured database.
|
|
// If it fails, it means it does not exist yet.
|
|
return $this->regularConn->createSchemaManager()->listTableNames();
|
|
} catch (Throwable) {
|
|
// We cannot use getDatabase() to get the database name here, because then the driver will try to connect.
|
|
// Instead, we read from the raw params.
|
|
$shlinkDatabase = $this->regularConn->getParams()['dbname'] ?? '';
|
|
// Create the database using a connection where the dbname was not set.
|
|
$this->noDbNameConn->createSchemaManager()->createDatabase($shlinkDatabase);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
}
|