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; } }