configWriter = $configWriter;
$this->isUpdate = $isUpdate;
$this->filesystem = $filesystem;
$this->configCustomizers = $configCustomizers;
$this->phpFinder = $phpFinder ?: new PhpExecutableFinder();
}
protected function configure(): void
{
$this
->setName('shlink:install')
->setDescription('Installs or updates Shlink');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function execute(InputInterface $input, OutputInterface $output): void
{
$this->io = new SymfonyStyle($input, $output);
$this->io->writeln([
'Welcome to Shlink!!',
'This tool will guide you through the installation process.',
]);
// Check if a cached config file exists and drop it if so
if ($this->filesystem->exists('data/cache/app_config.php')) {
$this->io->write('Deleting old cached config...');
try {
$this->filesystem->remove('data/cache/app_config.php');
$this->io->writeln(' Success');
} catch (IOException $e) {
$this->io->error(
'Failed! You will have to manually delete the data/cache/app_config.php file to'
. ' get new config applied.'
);
if ($this->io->isVerbose()) {
$this->getApplication()->renderException($e, $output);
}
return;
}
}
// If running update command, ask the user to import previous config
$config = $this->isUpdate ? $this->importConfig() : new CustomizableAppConfig();
// Ask for custom config params
foreach ([
Plugin\DatabaseConfigCustomizer::class,
Plugin\UrlShortenerConfigCustomizer::class,
Plugin\LanguageConfigCustomizer::class,
Plugin\ApplicationConfigCustomizer::class,
] as $pluginName) {
/** @var Plugin\ConfigCustomizerInterface $configCustomizer */
$configCustomizer = $this->configCustomizers->get($pluginName);
$configCustomizer->process($this->io, $config);
}
// Generate config params files
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
$this->io->writeln(['Custom configuration properly generated!', '']);
// If current command is not update, generate database
if (! $this->isUpdate) {
$this->io->write('Initializing database...');
if (! $this->runPhpCommand(
'vendor/doctrine/orm/bin/doctrine.php orm:schema-tool:create',
'Error generating database.',
$output
)) {
return;
}
}
// Run database migrations
$this->io->write('Updating database...');
if (! $this->runPhpCommand(
'vendor/doctrine/migrations/bin/doctrine-migrations.php migrations:migrate',
'Error updating database.',
$output
)) {
return;
}
// Generate proxies
$this->io->write('Generating proxies...');
if (! $this->runPhpCommand(
'vendor/doctrine/orm/bin/doctrine.php orm:generate-proxies',
'Error generating proxies.',
$output
)) {
return;
}
$this->io->success('Installation complete!');
}
/**
* @return CustomizableAppConfig
* @throws RuntimeException
*/
private function importConfig(): CustomizableAppConfig
{
$config = new CustomizableAppConfig();
// Ask the user if he/she wants to import an older configuration
$importConfig = $this->io->confirm('Do you want to import configuration from previous installation?');
if (! $importConfig) {
return $config;
}
// Ask the user for the older shlink path
$keepAsking = true;
do {
$config->setImportedInstallationPath($this->io->ask(
'Previous shlink installation path from which to import config'
));
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
$configExists = $this->filesystem->exists($configFile);
if (! $configExists) {
$keepAsking = $this->io->confirm(
'Provided path does not seem to be a valid shlink root path. Do you want to try another path?'
);
}
} while (! $configExists && $keepAsking);
// If after some retries the user has chosen not to test another path, return
if (! $configExists) {
return $config;
}
// Read the config file
$config->exchangeArray(include $configFile);
return $config;
}
/**
* @param string $command
* @param string $errorMessage
* @param OutputInterface $output
* @return bool
* @throws LogicException
* @throws InvalidArgumentException
*/
private function runPhpCommand($command, $errorMessage, OutputInterface $output): bool
{
if ($this->processHelper === null) {
$this->processHelper = $this->getHelper('process');
}
if ($this->phpBinary === null) {
$this->phpBinary = $this->phpFinder->find(false) ?: 'php';
}
$this->io->write(
' [Running "' . sprintf('%s %s', $this->phpBinary, $command) . '"]> ',
false,
OutputInterface::VERBOSITY_VERBOSE
);
$process = $this->processHelper->run($output, sprintf('%s %s', $this->phpBinary, $command));
if ($process->isSuccessful()) {
$this->io->writeln(' Success!');
return true;
}
if (! $this->io->isVerbose()) {
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
}
return false;
}
}