diff --git a/bin/update b/bin/update
index 86b7efc9..a10ef3f1 100755
--- a/bin/update
+++ b/bin/update
@@ -1,6 +1,6 @@
#!/usr/bin/env php
add($command);
$app->setDefaultCommand($command->getName());
$app->run();
diff --git a/module/CLI/src/Command/Install/AbstractInstallCommand.php b/module/CLI/src/Command/Install/AbstractInstallCommand.php
deleted file mode 100644
index f6341627..00000000
--- a/module/CLI/src/Command/Install/AbstractInstallCommand.php
+++ /dev/null
@@ -1,368 +0,0 @@
- 'pdo_mysql',
- 'PostgreSQL' => 'pdo_pgsql',
- 'SQLite' => 'pdo_sqlite',
- ];
- const SUPPORTED_LANGUAGES = ['en', 'es'];
- const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
-
- /**
- * @var InputInterface
- */
- protected $input;
- /**
- * @var OutputInterface
- */
- protected $output;
- /**
- * @var QuestionHelper
- */
- private $questionHelper;
- /**
- * @var ProcessHelper
- */
- private $processHelper;
- /**
- * @var WriterInterface
- */
- private $configWriter;
-
- /**
- * InstallCommand constructor.
- * @param WriterInterface $configWriter
- * @throws LogicException
- */
- public function __construct(WriterInterface $configWriter)
- {
- parent::__construct();
- $this->configWriter = $configWriter;
- }
-
- public function configure()
- {
- $this->setName('shlink:install')
- ->setDescription('Installs Shlink');
- }
-
- public function execute(InputInterface $input, OutputInterface $output)
- {
- $this->input = $input;
- $this->output = $output;
- $this->questionHelper = $this->getHelper('question');
- $this->processHelper = $this->getHelper('process');
- $params = [];
-
- $output->writeln([
- 'Welcome to Shlink!!',
- 'This will guide you through the installation process.',
- ]);
-
- // Check if a cached config file exists and drop it if so
- if (file_exists('data/cache/app_config.php')) {
- $output->write('Deleting old cached config...');
- if (unlink('data/cache/app_config.php')) {
- $output->writeln(' Success');
- } else {
- $output->writeln(
- ' Failed! You will have to manually delete the data/cache/app_config.php file to get'
- . ' new config applied.'
- );
- return;
- }
- }
-
- // If running update command, ask the user to import previous config
- if ($this->isUpdate()) {
- $this->importConfig();
- }
-
- // Ask for custom config params
- $params['DATABASE'] = $this->askDatabase();
- $params['URL_SHORTENER'] = $this->askUrlShortener();
- $params['LANGUAGE'] = $this->askLanguage();
- $params['APP'] = $this->askApplication();
-
- // Generate config params files
- $config = $this->buildAppConfig($params);
- $this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config, false);
- $output->writeln(['Custom configuration properly generated!', '']);
-
- // If current command is not update, generate database
- if (! $this->isUpdate()) {
- $this->output->writeln('Initializing database...');
- if (! $this->runCommand(
- 'php vendor/bin/doctrine.php orm:schema-tool:create',
- 'Error generating database.'
- )) {
- return;
- }
- }
-
- // Run database migrations
- $output->writeln('Updating database...');
- if (! $this->runCommand('php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.')) {
- return;
- }
-
- // Generate proxies
- $output->writeln('Generating proxies...');
- if (! $this->runCommand('php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.')) {
- return;
- }
- }
-
- protected function importConfig()
- {
- // Ask the user if he/she wants to import an older configuration
- $importConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
- 'Do you want to import previous configuration? (Y/n): '
- ));
- if (! $importConfig) {
- return;
- }
-
- // Ask the user for the older shlink path
- $keepAsking = true;
- do {
- $installationPath = $this->ask('Previous shlink installation path from which to import config');
- $configFile = $installationPath . '/' . self::GENERATED_CONFIG_PATH;
- $configExists = file_exists($configFile);
-
- if (! $configExists) {
- $keepAsking = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
- 'Provided path does not seem to be a valid shlink root path. '
- . 'Do you want to try another path? (Y/n): '
- ));
- }
- } while (! $configExists && $keepAsking);
-
- // Read the config file
- $previousConfig = include $configFile;
- }
-
- protected function askDatabase()
- {
- $params = [];
- $this->printTitle('DATABASE');
-
- // Select database type
- $databases = array_keys(self::DATABASE_DRIVERS);
- $dbType = $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
- 'Select database type (defaults to ' . $databases[0] . '):',
- $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('Database name', 'shlink');
- $params['USER'] = $this->ask('Database username');
- $params['PASSWORD'] = $this->ask('Database password');
- $params['HOST'] = $this->ask('Database host', 'localhost');
- $params['PORT'] = $this->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
- }
-
- return $params;
- }
-
- protected function getDefaultDbPort($driver)
- {
- return $driver === 'pdo_mysql' ? '3306' : '5432';
- }
-
- protected function askUrlShortener()
- {
- $this->printTitle('URL SHORTENER');
-
- // Ask for URL shortener params
- return [
- 'SCHEMA' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
- 'Select schema for generated short URLs (defaults to http):',
- ['http', 'https'],
- 0
- )),
- 'HOSTNAME' => $this->ask('Hostname for generated URLs'),
- 'CHARS' => $this->ask(
- 'Character set for generated short codes (leave empty to autogenerate one)',
- null,
- true
- ) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
- ];
- }
-
- protected function askLanguage()
- {
- $this->printTitle('LANGUAGE');
-
- return [
- 'DEFAULT' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
- 'Select default language for the application in general (defaults to '
- . self::SUPPORTED_LANGUAGES[0] . '):',
- self::SUPPORTED_LANGUAGES,
- 0
- )),
- 'CLI' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
- 'Select default language for CLI executions (defaults to '
- . self::SUPPORTED_LANGUAGES[0] . '):',
- self::SUPPORTED_LANGUAGES,
- 0
- )),
- ];
- }
-
- protected function askApplication()
- {
- $this->printTitle('APPLICATION');
-
- return [
- 'SECRET' => $this->ask(
- 'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
- null,
- true
- ) ?: $this->generateRandomString(32),
- ];
- }
-
- /**
- * @param string $text
- */
- protected function printTitle($text)
- {
- $text = trim($text);
- $length = strlen($text) + 4;
- $header = str_repeat('*', $length);
-
- $this->output->writeln([
- '',
- '' . $header . '',
- '* ' . strtoupper($text) . ' *',
- '' . $header . '',
- ]);
- }
-
- /**
- * @param string $text
- * @param string|null $default
- * @param bool $allowEmpty
- * @return string
- * @throws RuntimeException
- */
- protected function ask($text, $default = null, $allowEmpty = false)
- {
- if ($default !== null) {
- $text .= ' (defaults to ' . $default . ')';
- }
- do {
- $value = $this->questionHelper->ask($this->input, $this->output, new Question(
- '' . $text . ': ',
- $default
- ));
- if (empty($value) && ! $allowEmpty) {
- $this->output->writeln('Value can\'t be empty');
- }
- } while (empty($value) && $default === null && ! $allowEmpty);
-
- return $value;
- }
-
- /**
- * @param array $params
- * @return array
- */
- protected function buildAppConfig(array $params)
- {
- // Build simple config
- $config = [
- 'app_options' => [
- 'secret_key' => $params['APP']['SECRET'],
- ],
- 'entity_manager' => [
- 'connection' => [
- 'driver' => $params['DATABASE']['DRIVER'],
- ],
- ],
- 'translator' => [
- 'locale' => $params['LANGUAGE']['DEFAULT'],
- ],
- 'cli' => [
- 'locale' => $params['LANGUAGE']['CLI'],
- ],
- 'url_shortener' => [
- 'domain' => [
- 'schema' => $params['URL_SHORTENER']['SCHEMA'],
- 'hostname' => $params['URL_SHORTENER']['HOSTNAME'],
- ],
- 'shortcode_chars' => $params['URL_SHORTENER']['CHARS'],
- ],
- ];
-
- // Build dynamic database config
- if ($params['DATABASE']['DRIVER'] === 'pdo_sqlite') {
- $config['entity_manager']['connection']['path'] = 'data/database.sqlite';
- } else {
- $config['entity_manager']['connection']['user'] = $params['DATABASE']['USER'];
- $config['entity_manager']['connection']['password'] = $params['DATABASE']['PASSWORD'];
- $config['entity_manager']['connection']['dbname'] = $params['DATABASE']['NAME'];
- $config['entity_manager']['connection']['host'] = $params['DATABASE']['HOST'];
- $config['entity_manager']['connection']['port'] = $params['DATABASE']['PORT'];
-
- if ($params['DATABASE']['DRIVER'] === 'pdo_mysql') {
- $config['entity_manager']['connection']['driverOptions'] = [
- \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
- ];
- }
- }
-
- return $config;
- }
-
- /**
- * @param string $command
- * @param string $errorMessage
- * @return bool
- */
- protected function runCommand($command, $errorMessage)
- {
- $process = $this->processHelper->run($this->output, $command);
- if ($process->isSuccessful()) {
- $this->output->writeln(' Success!');
- return true;
- }
-
- if ($this->output->isVerbose()) {
- return false;
- }
-
- $this->output->writeln(
- ' ' . $errorMessage . ' Run this command with -vvv to see specific error info.'
- );
- return false;
- }
-
- /**
- * @return bool
- */
- abstract protected function isUpdate();
-}
diff --git a/module/CLI/src/Command/Install/InstallCommand.php b/module/CLI/src/Command/Install/InstallCommand.php
index e414bbe0..2ff9e9da 100644
--- a/module/CLI/src/Command/Install/InstallCommand.php
+++ b/module/CLI/src/Command/Install/InstallCommand.php
@@ -1,13 +1,363 @@
'pdo_mysql',
+ 'PostgreSQL' => 'pdo_pgsql',
+ 'SQLite' => 'pdo_sqlite',
+ ];
+ const SUPPORTED_LANGUAGES = ['en', 'es'];
+ const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
+
/**
+ * @var InputInterface
+ */
+ private $input;
+ /**
+ * @var OutputInterface
+ */
+ private $output;
+ /**
+ * @var QuestionHelper
+ */
+ private $questionHelper;
+ /**
+ * @var ProcessHelper
+ */
+ private $processHelper;
+ /**
+ * @var WriterInterface
+ */
+ private $configWriter;
+ /**
+ * @var bool
+ */
+ private $isUpdate;
+
+ /**
+ * InstallCommand constructor.
+ * @param WriterInterface $configWriter
+ * @param bool $isUpdate
+ * @throws LogicException
+ */
+ public function __construct(WriterInterface $configWriter, $isUpdate = false)
+ {
+ parent::__construct();
+ $this->configWriter = $configWriter;
+ $this->isUpdate = $isUpdate;
+ }
+
+ public function configure()
+ {
+ $this->setName('shlink:install')
+ ->setDescription('Installs Shlink');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->input = $input;
+ $this->output = $output;
+ $this->questionHelper = $this->getHelper('question');
+ $this->processHelper = $this->getHelper('process');
+
+ $output->writeln([
+ 'Welcome to Shlink!!',
+ 'This will guide you through the installation process.',
+ ]);
+
+ // Check if a cached config file exists and drop it if so
+ if (file_exists('data/cache/app_config.php')) {
+ $output->write('Deleting old cached config...');
+ if (unlink('data/cache/app_config.php')) {
+ $output->writeln(' Success');
+ } else {
+ $output->writeln(
+ ' Failed! You will have to manually delete the data/cache/app_config.php file to get'
+ . ' new config applied.'
+ );
+ return;
+ }
+ }
+
+ // If running update command, ask the user to import previous config
+ $config = $this->isUpdate ? $this->importConfig() : new CustomizableAppConfig();
+
+ // Ask for custom config params
+ $this->askDatabase($config);
+ $this->askUrlShortener($config);
+ $this->askLanguage($config);
+ $this->askApplication($config);
+
+ // Generate config params files
+ $this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
+ $output->writeln(['Custom configuration properly generated!', '']);
+
+ // If current command is not update, generate database
+ if (! $this->isUpdate) {
+ $this->output->writeln('Initializing database...');
+ if (! $this->runCommand(
+ 'php vendor/bin/doctrine.php orm:schema-tool:create',
+ 'Error generating database.'
+ )) {
+ return;
+ }
+ }
+
+ // Run database migrations
+ $output->writeln('Updating database...');
+ if (! $this->runCommand('php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.')) {
+ return;
+ }
+
+ // Generate proxies
+ $output->writeln('Generating proxies...');
+ if (! $this->runCommand('php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.')) {
+ return;
+ }
+ }
+
+ /**
+ * @return CustomizableAppConfig
+ * @throws RuntimeException
+ */
+ protected function importConfig()
+ {
+ $config = new CustomizableAppConfig();
+
+ // Ask the user if he/she wants to import an older configuration
+ $importConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
+ 'Do you want to import previous configuration? (Y/n): '
+ ));
+ if (! $importConfig) {
+ return $config;
+ }
+
+ // Ask the user for the older shlink path
+ $keepAsking = true;
+ do {
+ $installationPath = $this->ask('Previous shlink installation path from which to import config');
+ $configFile = $installationPath . '/' . self::GENERATED_CONFIG_PATH;
+ $configExists = file_exists($configFile);
+
+ if (! $configExists) {
+ $keepAsking = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
+ 'Provided path does not seem to be a valid shlink root path. '
+ . 'Do you want to try another path? (Y/n): '
+ ));
+ }
+ } 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;
+ }
+
+ protected function askDatabase(CustomizableAppConfig $config)
+ {
+ $this->printTitle('DATABASE');
+
+ if ($config->hasDatabase()) {
+ $keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
+ 'Do you want to keep imported database config? (Y/n): '
+ ));
+ if ($keepConfig) {
+ return;
+ }
+ }
+
+ // Select database type
+ $params = [];
+ $databases = array_keys(self::DATABASE_DRIVERS);
+ $dbType = $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
+ 'Select database type (defaults to ' . $databases[0] . '):',
+ $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('Database name', 'shlink');
+ $params['USER'] = $this->ask('Database username');
+ $params['PASSWORD'] = $this->ask('Database password');
+ $params['HOST'] = $this->ask('Database host', 'localhost');
+ $params['PORT'] = $this->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
+ }
+
+ $config->setDatabase($params);
+ }
+
+ protected function getDefaultDbPort($driver)
+ {
+ return $driver === 'pdo_mysql' ? '3306' : '5432';
+ }
+
+ protected function askUrlShortener(CustomizableAppConfig $config)
+ {
+ $this->printTitle('URL SHORTENER');
+
+ if ($config->hasUrlShortener()) {
+ $keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
+ 'Do you want to keep imported URL shortener config? (Y/n): '
+ ));
+ if ($keepConfig) {
+ return;
+ }
+ }
+
+ // Ask for URL shortener params
+ $config->setUrlShortener([
+ 'SCHEMA' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
+ 'Select schema for generated short URLs (defaults to http):',
+ ['http', 'https'],
+ 0
+ )),
+ 'HOSTNAME' => $this->ask('Hostname for generated URLs'),
+ 'CHARS' => $this->ask(
+ 'Character set for generated short codes (leave empty to autogenerate one)',
+ null,
+ true
+ ) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
+ ]);
+ }
+
+ protected function askLanguage(CustomizableAppConfig $config)
+ {
+ $this->printTitle('LANGUAGE');
+
+ if ($config->hasLanguage()) {
+ $keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
+ 'Do you want to keep imported language? (Y/n): '
+ ));
+ if ($keepConfig) {
+ return;
+ }
+ }
+
+ $config->setLanguage([
+ 'DEFAULT' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
+ 'Select default language for the application in general (defaults to '
+ . self::SUPPORTED_LANGUAGES[0] . '):',
+ self::SUPPORTED_LANGUAGES,
+ 0
+ )),
+ 'CLI' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
+ 'Select default language for CLI executions (defaults to '
+ . self::SUPPORTED_LANGUAGES[0] . '):',
+ self::SUPPORTED_LANGUAGES,
+ 0
+ )),
+ ]);
+ }
+
+ protected function askApplication(CustomizableAppConfig $config)
+ {
+ $this->printTitle('APPLICATION');
+
+ if ($config->hasApp()) {
+ $keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
+ 'Do you want to keep imported application config? (Y/n): '
+ ));
+ if ($keepConfig) {
+ return;
+ }
+ }
+
+ $config->setApp([
+ 'SECRET' => $this->ask(
+ 'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
+ null,
+ true
+ ) ?: $this->generateRandomString(32),
+ ]);
+ }
+
+ /**
+ * @param string $text
+ */
+ protected function printTitle($text)
+ {
+ $text = trim($text);
+ $length = strlen($text) + 4;
+ $header = str_repeat('*', $length);
+
+ $this->output->writeln([
+ '',
+ '' . $header . '',
+ '* ' . strtoupper($text) . ' *',
+ '' . $header . '',
+ ]);
+ }
+
+ /**
+ * @param string $text
+ * @param string|null $default
+ * @param bool $allowEmpty
+ * @return string
+ * @throws RuntimeException
+ */
+ protected function ask($text, $default = null, $allowEmpty = false)
+ {
+ if ($default !== null) {
+ $text .= ' (defaults to ' . $default . ')';
+ }
+ do {
+ $value = $this->questionHelper->ask($this->input, $this->output, new Question(
+ '' . $text . ': ',
+ $default
+ ));
+ if (empty($value) && ! $allowEmpty) {
+ $this->output->writeln('Value can\'t be empty');
+ }
+ } while (empty($value) && $default === null && ! $allowEmpty);
+
+ return $value;
+ }
+
+ /**
+ * @param string $command
+ * @param string $errorMessage
* @return bool
*/
- protected function isUpdate()
+ protected function runCommand($command, $errorMessage)
{
+ $process = $this->processHelper->run($this->output, $command);
+ if ($process->isSuccessful()) {
+ $this->output->writeln(' Success!');
+ return true;
+ }
+
+ if ($this->output->isVerbose()) {
+ return false;
+ }
+
+ $this->output->writeln(
+ ' ' . $errorMessage . ' Run this command with -vvv to see specific error info.'
+ );
return false;
}
}
diff --git a/module/CLI/src/Command/Install/UpdateCommand.php b/module/CLI/src/Command/Install/UpdateCommand.php
deleted file mode 100644
index 425dc06a..00000000
--- a/module/CLI/src/Command/Install/UpdateCommand.php
+++ /dev/null
@@ -1,13 +0,0 @@
-database;
+ }
+
+ /**
+ * @param array $database
+ * @return $this
+ */
+ public function setDatabase(array $database)
+ {
+ $this->database = $database;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasDatabase()
+ {
+ return ! empty($this->database);
+ }
+
+ /**
+ * @return array
+ */
+ public function getUrlShortener()
+ {
+ return $this->urlShortener;
+ }
+
+ /**
+ * @param array $urlShortener
+ * @return $this
+ */
+ public function setUrlShortener(array $urlShortener)
+ {
+ $this->urlShortener = $urlShortener;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasUrlShortener()
+ {
+ return ! empty($this->urlShortener);
+ }
+
+ /**
+ * @return array
+ */
+ public function getLanguage()
+ {
+ return $this->language;
+ }
+
+ /**
+ * @param array $language
+ * @return $this
+ */
+ public function setLanguage(array $language)
+ {
+ $this->language = $language;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasLanguage()
+ {
+ return ! empty($this->language);
+ }
+
+ /**
+ * @return array
+ */
+ public function getApp()
+ {
+ return $this->app;
+ }
+
+ /**
+ * @param array $app
+ * @return $this
+ */
+ public function setApp(array $app)
+ {
+ $this->app = $app;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasApp()
+ {
+ return ! empty($this->app);
+ }
+
+ /**
+ * Exchange internal values from provided array
+ *
+ * @param array $array
+ * @return void
+ */
+ public function exchangeArray(array $array)
+ {
+
+ }
+
+ /**
+ * Return an array representation of the object
+ *
+ * @return array
+ */
+ public function getArrayCopy()
+ {
+ $config = [
+ 'app_options' => [
+ 'secret_key' => $this->app['SECRET'],
+ ],
+ 'entity_manager' => [
+ 'connection' => [
+ 'driver' => $this->database['DRIVER'],
+ ],
+ ],
+ 'translator' => [
+ 'locale' => $this->language['DEFAULT'],
+ ],
+ 'cli' => [
+ 'locale' => $this->language['CLI'],
+ ],
+ 'url_shortener' => [
+ 'domain' => [
+ 'schema' => $this->urlShortener['SCHEMA'],
+ 'hostname' => $this->urlShortener['HOSTNAME'],
+ ],
+ 'shortcode_chars' => $this->urlShortener['CHARS'],
+ ],
+ ];
+
+ // Build dynamic database config based on selected driver
+ if ($this->database['DRIVER'] === 'pdo_sqlite') {
+ $config['entity_manager']['connection']['path'] = 'data/database.sqlite';
+ } else {
+ $config['entity_manager']['connection']['user'] = $this->database['USER'];
+ $config['entity_manager']['connection']['password'] = $this->database['PASSWORD'];
+ $config['entity_manager']['connection']['dbname'] = $this->database['NAME'];
+ $config['entity_manager']['connection']['host'] = $this->database['HOST'];
+ $config['entity_manager']['connection']['port'] = $this->database['PORT'];
+
+ if ($this->database['DRIVER'] === 'pdo_mysql') {
+ $config['entity_manager']['connection']['driverOptions'] = [
+ \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
+ ];
+ }
+ }
+
+ return $config;
+ }
+}
diff --git a/module/CLI/test/Command/Install/InstallCommandTest.php b/module/CLI/test/Command/Install/InstallCommandTest.php
index ebfb7e29..4b49d7ec 100644
--- a/module/CLI/test/Command/Install/InstallCommandTest.php
+++ b/module/CLI/test/Command/Install/InstallCommandTest.php
@@ -104,6 +104,7 @@ CLI_INPUT
'shortcode_chars' => 'abc123BCA',
],
], false)->shouldBeCalledTimes(1);
+
$this->commandTester->execute([
'command' => 'shlink:install',
]);
diff --git a/module/Common/src/Util/StringUtilsTrait.php b/module/Common/src/Util/StringUtilsTrait.php
index 648dbfcb..9680aa49 100644
--- a/module/Common/src/Util/StringUtilsTrait.php
+++ b/module/Common/src/Util/StringUtilsTrait.php
@@ -9,7 +9,7 @@ trait StringUtilsTrait
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
- $randomString .= $characters[rand(0, $charactersLength - 1)];
+ $randomString .= $characters[mt_rand(0, $charactersLength - 1)];
}
return $randomString;