Moved all configuration customization steps to individual plugins

This commit is contained in:
Alejandro Celaya
2017-07-05 20:04:44 +02:00
parent 2368b634e3
commit 479e694478
15 changed files with 475 additions and 179 deletions

View File

@@ -0,0 +1,10 @@
<?php
namespace Shlinkio\Shlink\CLI\Install;
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface;
use Zend\ServiceManager\AbstractPluginManager;
class ConfigCustomizerPluginManager extends AbstractPluginManager implements ConfigCustomizerPluginManagerInterface
{
protected $instanceOf = ConfigCustomizerPluginInterface::class;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Shlinkio\Shlink\CLI\Install;
use Psr\Container\ContainerInterface;
interface ConfigCustomizerPluginManagerInterface extends ContainerInterface
{
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Shlinkio\Shlink\CLI\Install\Plugin;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginInterface
{
/**
* @var QuestionHelper
*/
protected $questionHelper;
public function __construct(QuestionHelper $questionHelper)
{
$this->questionHelper = $questionHelper;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $text
* @param string|null $default
* @param bool $allowEmpty
* @return string
* @throws RuntimeException
*/
protected function ask(InputInterface $input, OutputInterface $output, $text, $default = null, $allowEmpty = false)
{
if ($default !== null) {
$text .= ' (defaults to ' . $default . ')';
}
do {
$value = $this->questionHelper->ask($input, $output, new Question(
'<question>' . $text . ':</question> ',
$default
));
if (empty($value) && ! $allowEmpty) {
$output->writeln('<error>Value can\'t be empty</error>');
}
} while (empty($value) && $default === null && ! $allowEmpty);
return $value;
}
/**
* @param OutputInterface $output
* @param string $text
*/
protected function printTitle(OutputInterface $output, $text)
{
$text = trim($text);
$length = strlen($text) + 4;
$header = str_repeat('*', $length);
$output->writeln([
'',
'<info>' . $header . '</info>',
'<info>* ' . strtoupper($text) . ' *</info>',
'<info>' . $header . '</info>',
]);
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Install\Plugin;
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
{
use StringUtilsTrait;
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param CustomizableAppConfig $appConfig
* @return void
* @throws \Symfony\Component\Console\Exception\RuntimeException
*/
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
{
$this->printTitle($output, 'APPLICATION');
if ($appConfig->hasApp()) {
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
'<question>Do you want to keep imported application config? (Y/n):</question> '
));
if ($keepConfig) {
return;
}
}
$appConfig->setApp([
'SECRET' => $this->ask(
$input,
$output,
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
null,
true
) ?: $this->generateRandomString(32),
]);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Shlinkio\Shlink\CLI\Install\Plugin;
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
interface ConfigCustomizerPluginInterface
{
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param CustomizableAppConfig $appConfig
* @return void
*/
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig);
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Shlinkio\Shlink\CLI\Install\Plugin;
use Acelaya\ZsmAnnotatedServices\Annotation as DI;
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
{
const DATABASE_DRIVERS = [
'MySQL' => 'pdo_mysql',
'PostgreSQL' => 'pdo_pgsql',
'SQLite' => 'pdo_sqlite',
];
/**
* @var Filesystem
*/
private $filesystem;
/**
* DatabaseConfigCustomizerPlugin constructor.
* @param QuestionHelper $questionHelper
* @param Filesystem $filesystem
*
* @DI\Inject({QuestionHelper::class, Filesystem::class})
*/
public function __construct(QuestionHelper $questionHelper, Filesystem $filesystem)
{
parent::__construct($questionHelper);
$this->filesystem = $filesystem;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param CustomizableAppConfig $appConfig
* @return void
* @throws IOException
* @throws RuntimeException
*/
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
{
$this->printTitle($output, 'DATABASE');
if ($appConfig->hasDatabase()) {
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
'<question>Do you want to keep imported database config? (Y/n):</question> '
));
if ($keepConfig) {
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
try {
$this->filesystem->copy(
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
CustomizableAppConfig::SQLITE_DB_PATH
);
} catch (IOException $e) {
$output->writeln('<error>It wasn\'t possible to import the SQLite database</error>');
throw $e;
}
}
return;
}
}
// Select database type
$params = [];
$databases = array_keys(self::DATABASE_DRIVERS);
$dbType = $this->questionHelper->ask($input, $output, new ChoiceQuestion(
'<question>Select database type (defaults to ' . $databases[0] . '):</question>',
$databases,
0
));
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
// Ask for connection params if database is not SQLite
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
$params['NAME'] = $this->ask($input, $output, 'Database name', 'shlink');
$params['USER'] = $this->ask($input, $output, 'Database username');
$params['PASSWORD'] = $this->ask($input, $output, 'Database password');
$params['HOST'] = $this->ask($input, $output, 'Database host', 'localhost');
$params['PORT'] = $this->ask($input, $output, 'Database port', $this->getDefaultDbPort($params['DRIVER']));
}
$appConfig->setDatabase($params);
}
private function getDefaultDbPort($driver)
{
return $driver === 'pdo_mysql' ? '3306' : '5432';
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Install\Plugin\Factory;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Symfony\Component\Console\Helper\QuestionHelper;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
class DefaultConfigCustomizerPluginFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
* @return object
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new $requestedName($container->get(QuestionHelper::class));
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Install\Plugin;
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
{
const SUPPORTED_LANGUAGES = ['en', 'es'];
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param CustomizableAppConfig $appConfig
* @return void
* @throws RuntimeException
*/
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
{
$this->printTitle($output, 'LANGUAGE');
if ($appConfig->hasLanguage()) {
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
'<question>Do you want to keep imported language? (Y/n):</question> '
));
if ($keepConfig) {
return;
}
}
$appConfig->setLanguage([
'DEFAULT' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
'<question>Select default language for the application in general (defaults to '
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
self::SUPPORTED_LANGUAGES,
0
)),
'CLI' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
'<question>Select default language for CLI executions (defaults to '
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
self::SUPPORTED_LANGUAGES,
0
)),
]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Install\Plugin;
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
use Shlinkio\Shlink\Core\Service\UrlShortener;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
{
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param CustomizableAppConfig $appConfig
* @return void
* @throws RuntimeException
*/
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
{
$this->printTitle($output, 'URL SHORTENER');
if ($appConfig->hasUrlShortener()) {
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
'<question>Do you want to keep imported URL shortener config? (Y/n):</question> '
));
if ($keepConfig) {
return;
}
}
// Ask for URL shortener params
$appConfig->setUrlShortener([
'SCHEMA' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
'<question>Select schema for generated short URLs (defaults to http):</question>',
['http', 'https'],
0
)),
'HOSTNAME' => $this->ask($input, $output, 'Hostname for generated URLs'),
'CHARS' => $this->ask(
$input,
$output,
'Character set for generated short codes (leave empty to autogenerate one)',
null,
true
) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
]);
}
}