Skip to content

Commit b0a204a

Browse files
authored
Merge pull request #255 from acelaya-forks/symfony-cli-improvements
Convert InitCommand into invokable command
2 parents 40e08cb + 1be30af commit b0a204a

File tree

7 files changed

+86
-167
lines changed

7 files changed

+86
-167
lines changed

src/Command/InitCommand.php

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,37 @@
44

55
namespace Shlinkio\Shlink\Installer\Command;
66

7-
use Shlinkio\Shlink\Installer\Command\Model\InitOption;
8-
use Shlinkio\Shlink\Installer\Model\CLIOption;
9-
use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;
7+
use Shlinkio\Shlink\Installer\Command\Model\InitCommandInput;
108
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
119
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
10+
use Symfony\Component\Console\Attribute\AsCommand;
11+
use Symfony\Component\Console\Attribute\MapInput;
1212
use Symfony\Component\Console\Command\Command;
1313
use Symfony\Component\Console\Input\InputInterface;
14-
use Symfony\Component\Console\Output\OutputInterface;
1514
use Symfony\Component\Console\Style\SymfonyStyle;
1615

1716
use function array_reduce;
1817

18+
#[AsCommand(
19+
name: InitCommand::NAME,
20+
description: 'Initializes external dependencies required for Shlink to properly work, like DB, cache warmup, '
21+
. 'initial GeoLite DB download, etc',
22+
)]
1923
class InitCommand extends Command
2024
{
2125
public const string NAME = 'init';
2226

23-
private readonly CLIOption $skipInitDb;
24-
private readonly CLIOption $clearDbCache;
25-
private readonly CLIOption $initialApiKey;
26-
private readonly CLIOption $downloadRoadRunnerBin;
27-
private readonly CLIOption $skipDownloadGeoLiteDb;
28-
2927
public function __construct(private readonly InstallationCommandsRunnerInterface $commandsRunner)
3028
{
3129
parent::__construct();
32-
33-
$this->initialApiKey = InitOption::INITIAL_API_KEY->toCLIOption($this);
34-
$this->skipInitDb = InitOption::SKIP_INITIALIZE_DB->toCLIOption($this);
35-
$this->clearDbCache = InitOption::CLEAR_DB_CACHE->toCLIOption($this);
36-
$this->downloadRoadRunnerBin = InitOption::DOWNLOAD_RR_BINARY->toCLIOption($this);
37-
$this->skipDownloadGeoLiteDb = InitOption::SKIP_DOWNLOAD_GEOLITE->toCLIOption($this);
38-
}
39-
40-
protected function configure(): void
41-
{
42-
$this
43-
->setName(self::NAME)
44-
->setDescription(
45-
'Initializes external dependencies required for Shlink to properly work, like DB, cache warmup, '
46-
. 'initial GeoLite DB download, etc',
47-
);
4830
}
4931

50-
protected function execute(InputInterface $input, OutputInterface $output): int
32+
public function __invoke(SymfonyStyle $io, InputInterface $input, #[MapInput] InitCommandInput $inputData): int
5133
{
52-
$config = new ShlinkInitConfig(
53-
initializeDb: ! $this->skipInitDb->get($input),
54-
clearDbCache: $this->clearDbCache->get($input),
55-
downloadRoadrunnerBinary: $this->downloadRoadRunnerBin->get($input),
56-
generateApiKey: $this->initialApiKey->get($input),
57-
downloadGeoLiteDb: ! $this->skipDownloadGeoLiteDb->get($input),
58-
);
59-
$commands = [...InstallationCommand::resolveCommandsForConfig($config)];
60-
$io = new SymfonyStyle($input, $output);
34+
$commands = [...$inputData->resolveCommands()];
6135

6236
return array_reduce($commands, function (bool $carry, array $commandInfo) use ($input, $io): bool {
63-
/** @var array{InstallationCommand, string | null} $commandInfo */
37+
/** @var array{InstallationCommand, string|null} $commandInfo */
6438
[$command, $arg] = $commandInfo;
6539

6640
return $this->commandsRunner->execPhpCommand(
@@ -69,6 +43,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6943
interactive: $input->isInteractive(),
7044
args: $arg !== null ? [$arg] : [],
7145
) && $carry;
72-
}, initial: true) ? 0 : -1;
46+
}, initial: true) ? self::SUCCESS : self::FAILURE;
7347
}
7448
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shlinkio\Shlink\Installer\Command\Model;
6+
7+
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
8+
use Symfony\Component\Console\Attribute\Option;
9+
10+
use function is_string;
11+
12+
final class InitCommandInput
13+
{
14+
#[Option(
15+
'Skip the initial empty database creation. It will make this command fail on a later stage if the '
16+
. 'database was not created manually',
17+
)]
18+
public bool $skipInitializeDb = false;
19+
20+
#[Option('Clear the database metadata cache')]
21+
public bool $clearDbCache = false;
22+
23+
/**
24+
* False: Do not generate an initial API key.
25+
* True: Auto-generate a random initial API key.
26+
* String: Use provided value as the initial API key.
27+
*/
28+
#[Option('Create an initial API key. A random one will be generated and printed if no value is provided')]
29+
public string|bool $initialApiKey = false;
30+
31+
#[Option('Download a RoadRunner binary. Useful only if you plan to serve Shlink with Roadrunner')]
32+
public bool $downloadRrBinary = false;
33+
34+
#[Option(
35+
'Skip downloading the initial GeoLite DB file. Shlink will try to download it the first time it needs '
36+
. 'to geolocate visits',
37+
)]
38+
public bool $skipDownloadGeolite = false;
39+
40+
/**
41+
* @return iterable<array{InstallationCommand, string|null}>
42+
*/
43+
public function resolveCommands(): iterable
44+
{
45+
if (! $this->skipInitializeDb) {
46+
yield [InstallationCommand::DB_CREATE_SCHEMA, null];
47+
}
48+
49+
yield [InstallationCommand::DB_MIGRATE, null];
50+
yield [InstallationCommand::ORM_PROXIES, null];
51+
52+
if ($this->clearDbCache) {
53+
yield [InstallationCommand::ORM_CLEAR_CACHE, null];
54+
}
55+
56+
if (! $this->skipDownloadGeolite) {
57+
yield [InstallationCommand::GEOLITE_DOWNLOAD_DB, null];
58+
}
59+
60+
if ($this->initialApiKey === true) {
61+
yield [InstallationCommand::API_KEY_GENERATE, null];
62+
} elseif (is_string($this->initialApiKey)) {
63+
yield [InstallationCommand::API_KEY_CREATE, $this->initialApiKey];
64+
}
65+
66+
if ($this->downloadRrBinary) {
67+
yield [InstallationCommand::ROAD_RUNNER_BINARY_DOWNLOAD, null];
68+
}
69+
}
70+
}

src/Command/Model/InitOption.php

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
namespace Shlinkio\Shlink\Installer\Command\Model;
44

5-
use Shlinkio\Shlink\Installer\Model\CLIOption;
6-
use Symfony\Component\Console\Command\Command;
7-
use Symfony\Component\Console\Input\InputOption;
8-
5+
/** @deprecated */
96
enum InitOption: string
107
{
118
case SKIP_INITIALIZE_DB = 'skip-initialize-db';
@@ -18,42 +15,4 @@ public function asCliFlag(): string
1815
{
1916
return '--' . $this->value;
2017
}
21-
22-
public function description(): string
23-
{
24-
return match ($this) {
25-
self::SKIP_INITIALIZE_DB =>
26-
'Skip the initial empty database creation. It will make this command fail on a later stage if the '
27-
. 'database was not created manually.',
28-
self::CLEAR_DB_CACHE => 'Clear the database metadata cache.',
29-
self::INITIAL_API_KEY =>
30-
'Create an initial admin API key. A random one will be generated and printed if no value is provided.',
31-
self::DOWNLOAD_RR_BINARY =>
32-
'Download a RoadRunner binary. Useful only if you plan to serve Shlink with Roadrunner.',
33-
self::SKIP_DOWNLOAD_GEOLITE =>
34-
'Skip downloading the initial GeoLite DB file. Shlink will try to download it the first time it needs '
35-
. 'to geolocate visits.',
36-
};
37-
}
38-
39-
public function valueType(): int
40-
{
41-
return match ($this) {
42-
self::INITIAL_API_KEY => InputOption::VALUE_OPTIONAL,
43-
default => InputOption::VALUE_NONE,
44-
};
45-
}
46-
47-
public function defaultValue(): bool|null
48-
{
49-
return match ($this) {
50-
self::INITIAL_API_KEY => false,
51-
default => null,
52-
};
53-
}
54-
55-
public function toCLIOption(Command $command): CLIOption
56-
{
57-
return new CLIOption($command, $this);
58-
}
5918
}

src/Model/CLIOption.php

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/Model/ShlinkInitConfig.php

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/Util/InstallationCommand.php

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44

55
namespace Shlinkio\Shlink\Installer\Util;
66

7-
use Shlinkio\Shlink\Installer\Model\ShlinkInitConfig;
8-
9-
use function is_string;
10-
117
enum InstallationCommand: string
128
{
139
case DB_CREATE_SCHEMA = 'db_create_schema';
@@ -18,35 +14,4 @@ enum InstallationCommand: string
1814
case API_KEY_GENERATE = 'api_key_generate';
1915
case API_KEY_CREATE = 'api_key_create';
2016
case ROAD_RUNNER_BINARY_DOWNLOAD = 'road_runner_update';
21-
22-
/**
23-
* @return iterable<array{self, string | null}>
24-
*/
25-
public static function resolveCommandsForConfig(ShlinkInitConfig $config): iterable
26-
{
27-
if ($config->initializeDb) {
28-
yield [self::DB_CREATE_SCHEMA, null];
29-
}
30-
31-
yield [self::DB_MIGRATE, null];
32-
yield [self::ORM_PROXIES, null];
33-
34-
if ($config->clearDbCache) {
35-
yield [self::ORM_CLEAR_CACHE, null];
36-
}
37-
38-
if ($config->downloadGeoLiteDb) {
39-
yield [self::GEOLITE_DOWNLOAD_DB, null];
40-
}
41-
42-
if ($config->generateApiKey === null) {
43-
yield [self::API_KEY_GENERATE, null];
44-
} elseif (is_string($config->generateApiKey)) {
45-
yield [self::API_KEY_CREATE, $config->generateApiKey];
46-
}
47-
48-
if ($config->downloadRoadrunnerBinary) {
49-
yield [self::ROAD_RUNNER_BINARY_DOWNLOAD, null];
50-
}
51-
}
5217
}

test/Command/InitCommandTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Shlinkio\Shlink\Installer\Service\InstallationCommandsRunnerInterface;
1414
use Shlinkio\Shlink\Installer\Util\InstallationCommand;
1515
use Symfony\Component\Console\Application;
16+
use Symfony\Component\Console\Command\Command;
1617
use Symfony\Component\Console\Tester\CommandTester;
1718

1819
use function count;
@@ -111,7 +112,7 @@ public function properExitCodeIsReturnedBasedOnCommandsExecution(bool $result, i
111112

112113
public static function provideExitCodes(): iterable
113114
{
114-
yield 'success' => [true, 0];
115-
yield 'error' => [false, -1];
115+
yield 'success' => [true, Command::SUCCESS];
116+
yield 'error' => [false, Command::FAILURE];
116117
}
117118
}

0 commit comments

Comments
 (0)