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->execPhp( ['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->execPhp( ['vendor/doctrine/migrations/bin/doctrine-migrations.php', 'migrations:migrate'], 'Error updating database.', $output )) { return; } // Generate proxies $this->io->write('Generating proxies...'); if (! $this->execPhp( ['vendor/doctrine/orm/bin/doctrine.php', 'orm:generate-proxies'], 'Error generating proxies.', $output )) { return; } // Download GeoLite2 db file $this->io->write('Downloading GeoLite2 db...'); if (! $this->execPhp(['bin/cli', 'visit:update-db'], 'Error downloading GeoLite2 db.', $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? (You will still be asked for any new ' . 'config option that did not exist in previous shlink versions)' ); if (! $importConfig) { return $config; } // Ask the user for the older shlink path $keepAsking = true; do { $config->setImportedInstallationPath($this->askRequired( $this->io, 'previous installation path', '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; } private function execPhp(array $command, string $errorMessage, OutputInterface $output): bool { if ($this->processHelper === null) { $this->processHelper = $this->getHelper('process'); } if ($this->phpBinary === null) { $this->phpBinary = $this->phpFinder->find(false) ?: 'php'; } array_unshift($command, $this->phpBinary); $this->io->write( ' [Running "' . implode(' ', $command) . '"] ', false, OutputInterface::VERBOSITY_VERBOSE ); $process = $this->processHelper->run($output, $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; } }