Completed
Push — master ( f7424d...b53e51 )
by Alejandro
07:43
created

InstallCommand::execute()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 54
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 6.9157

Importance

Changes 0
Metric Value
cc 6
eloc 32
nc 12
nop 2
dl 0
loc 54
rs 8.7449
c 0
b 0
f 0
ccs 24
cts 34
cp 0.7059
crap 6.9157

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Shlinkio\Shlink\CLI\Command\Install;
3
4
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
5
use Shlinkio\Shlink\Core\Service\UrlShortener;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Helper\ProcessHelper;
8
use Symfony\Component\Console\Helper\QuestionHelper;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Console\Question\ChoiceQuestion;
12
use Symfony\Component\Console\Question\Question;
13
use Zend\Config\Writer\WriterInterface;
14
15
class InstallCommand extends Command
16
{
17
    use StringUtilsTrait;
18
19
    const DATABASE_DRIVERS = [
20
        'MySQL' => 'pdo_mysql',
21
        'PostgreSQL' => 'pdo_pgsql',
22
        'SQLite' => 'pdo_sqlite',
23
    ];
24
    const SUPPORTED_LANGUAGES = ['en', 'es'];
25
26
    /**
27
     * @var InputInterface
28
     */
29
    private $input;
30
    /**
31
     * @var OutputInterface
32
     */
33
    private $output;
34
    /**
35
     * @var QuestionHelper
36
     */
37
    private $questionHelper;
38
    /**
39
     * @var ProcessHelper
40
     */
41
    private $processHelper;
42
    /**
43
     * @var WriterInterface
44
     */
45
    private $configWriter;
46
47
    /**
48
     * InstallCommand constructor.
49
     * @param WriterInterface $configWriter
50
     * @param callable|null $databaseCreationLogic
0 ignored issues
show
Bug introduced by
There is no parameter named $databaseCreationLogic. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
51
     */
52 1
    public function __construct(WriterInterface $configWriter)
53
    {
54 1
        parent::__construct(null);
55 1
        $this->configWriter = $configWriter;
56 1
    }
57
58 1
    public function configure()
59
    {
60 1
        $this->setName('shlink:install')
61 1
             ->setDescription('Installs Shlink');
62 1
    }
63
64 1
    public function execute(InputInterface $input, OutputInterface $output)
65
    {
66 1
        $this->input = $input;
67 1
        $this->output = $output;
68 1
        $this->questionHelper = $this->getHelper('question');
69 1
        $this->processHelper = $this->getHelper('process');
70 1
        $params = [];
71
72 1
        $output->writeln([
73 1
            '<info>Welcome to Shlink!!</info>',
74 1
            'This process will guide you through the installation.',
75 1
        ]);
76
77
        // Check if a cached config file exists and drop it if so
78 1
        if (file_exists('data/cache/app_config.php')) {
79
            $output->write('Deleting old cached config...');
80
            if (unlink('data/cache/app_config.php')) {
81
                $output->writeln(' <info>Success</info>');
82
            } else {
83
                $output->writeln(
84
                    ' <error>Failed!</error> You will have to manually delete the data/cache/app_config.php file to get'
85
                    . ' new config applied.'
86
                );
87
            }
88
        }
89
90
        // Ask for custom config params
91 1
        $params['DATABASE'] = $this->askDatabase();
92 1
        $params['URL_SHORTENER'] = $this->askUrlShortener();
93 1
        $params['LANGUAGE'] = $this->askLanguage();
94 1
        $params['APP'] = $this->askApplication();
95
96
        // Generate config params files
97 1
        $config = $this->buildAppConfig($params);
98 1
        $this->configWriter->toFile('config/params/generated_config.php', $config, false);
99 1
        $output->writeln(['<info>Custom configuration properly generated!</info>', '']);
100
101
        // Generate database
102 1
        if (! $this->createDatabase()) {
103
            return;
104
        }
105
106
        // Run database migrations
107 1
        $output->writeln('Updating database...');
108 1
        if (! $this->runCommand('php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.')) {
109
            return;
110
        }
111
112
        // Generate proxies
113 1
        $output->writeln('Generating proxies...');
114 1
        if (! $this->runCommand('php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.')) {
115
            return;
116
        }
117 1
    }
118
119 1
    protected function askDatabase()
120
    {
121 1
        $params = [];
122 1
        $this->printTitle('DATABASE');
123
124
        // Select database type
125 1
        $databases = array_keys(self::DATABASE_DRIVERS);
126 1
        $dbType = $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
127 1
            '<question>Select database type (defaults to ' . $databases[0] . '):</question>',
128 1
            $databases,
129
            0
130 1
        ));
131 1
        $params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
132
133
        // Ask for connection params if database is not SQLite
134 1
        if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
135 1
            $params['NAME'] = $this->ask('Database name', 'shlink');
136 1
            $params['USER'] = $this->ask('Database username');
137 1
            $params['PASSWORD'] = $this->ask('Database password');
138 1
            $params['HOST'] = $this->ask('Database host', 'localhost');
139 1
            $params['PORT'] = $this->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
140 1
        }
141
142 1
        return $params;
143
    }
144
145 1
    protected function getDefaultDbPort($driver)
146
    {
147 1
        return $driver === 'pdo_mysql' ? '3306' : '5432';
148
    }
149
150 1
    protected function askUrlShortener()
151
    {
152 1
        $this->printTitle('URL SHORTENER');
153
154
        // Ask for URL shortener params
155
        return [
156 1
            'SCHEMA' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
157 1
                '<question>Select schema for generated short URLs (defaults to http):</question>',
158 1
                ['http', 'https'],
159
                0
160 1
            )),
161 1
            'HOSTNAME' => $this->ask('Hostname for generated URLs'),
162 1
            'CHARS' => $this->ask(
163 1
                'Character set for generated short codes (leave empty to autogenerate one)',
164 1
                null,
165
                true
166 1
            ) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
167 1
        ];
168
    }
169
170 1
    protected function askLanguage()
171
    {
172 1
        $this->printTitle('LANGUAGE');
173
174
        return [
175 1
            'DEFAULT' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
176
                '<question>Select default language for the application in general (defaults to '
177 1
                . self::SUPPORTED_LANGUAGES[0] . '):</question>',
178 1
                self::SUPPORTED_LANGUAGES,
179
                0
180 1
            )),
181 1
            'CLI' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
182
                '<question>Select default language for CLI executions (defaults to '
183 1
                . self::SUPPORTED_LANGUAGES[0] . '):</question>',
184 1
                self::SUPPORTED_LANGUAGES,
185
                0
186 1
            )),
187 1
        ];
188
    }
189
190 1
    protected function askApplication()
191
    {
192 1
        $this->printTitle('APPLICATION');
193
194
        return [
195 1
            'SECRET' => $this->ask(
196 1
                'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
197 1
                null,
198
                true
199 1
            ) ?: $this->generateRandomString(32),
200 1
        ];
201
    }
202
203
    /**
204
     * @param string $text
205
     */
206 1
    protected function printTitle($text)
207
    {
208 1
        $text = trim($text);
209 1
        $length = strlen($text) + 4;
210 1
        $header = str_repeat('*', $length);
211
212 1
        $this->output->writeln([
213 1
            '',
214 1
            '<info>' . $header . '</info>',
215 1
            '<info>* ' . strtoupper($text) . ' *</info>',
216 1
            '<info>' . $header . '</info>',
217 1
        ]);
218 1
    }
219
220
    /**
221
     * @param string $text
222
     * @param string|null $default
223
     * @param bool $allowEmpty
224
     * @return string
225
     */
226 1
    protected function ask($text, $default = null, $allowEmpty = false)
227
    {
228 1
        if (isset($default)) {
229 1
            $text .= ' (defaults to ' . $default . ')';
230 1
        }
231
        do {
232 1
            $value = $this->questionHelper->ask($this->input, $this->output, new Question(
233 1
                '<question>' . $text . ':</question> ',
234
                $default
235 1
            ));
236 1
            if (empty($value) && ! $allowEmpty) {
237
                $this->output->writeln('<error>Value can\'t be empty</error>');
238
            }
239 1
        } while (empty($value) && empty($default) && ! $allowEmpty);
240
241 1
        return $value;
242
    }
243
244
    /**
245
     * @param array $params
246
     * @return array
247
     */
248 1
    protected function buildAppConfig(array $params)
249
    {
250
        // Build simple config
251
        $config = [
252
            'app_options' => [
253 1
                'secret_key' => $params['APP']['SECRET'],
254 1
            ],
255
            'entity_manager' => [
256
                'connection' => [
257 1
                    'driver' => $params['DATABASE']['DRIVER'],
258 1
                ],
259 1
            ],
260
            'translator' => [
261 1
                'locale' => $params['LANGUAGE']['DEFAULT'],
262 1
            ],
263
            'cli' => [
264 1
                'locale' => $params['LANGUAGE']['CLI'],
265 1
            ],
266
            'url_shortener' => [
267
                'domain' => [
268 1
                    'schema' => $params['URL_SHORTENER']['SCHEMA'],
269 1
                    'hostname' => $params['URL_SHORTENER']['HOSTNAME'],
270 1
                ],
271 1
                'shortcode_chars' => $params['URL_SHORTENER']['CHARS'],
272 1
            ],
273 1
        ];
274
275
        // Build dynamic database config
276 1
        if ($params['DATABASE']['DRIVER'] === 'pdo_sqlite') {
277
            $config['entity_manager']['connection']['path'] = 'data/database.sqlite';
278
        } else {
279 1
            $config['entity_manager']['connection']['user'] = $params['DATABASE']['USER'];
280 1
            $config['entity_manager']['connection']['password'] = $params['DATABASE']['PASSWORD'];
281 1
            $config['entity_manager']['connection']['dbname'] = $params['DATABASE']['NAME'];
282 1
            $config['entity_manager']['connection']['host'] = $params['DATABASE']['HOST'];
283 1
            $config['entity_manager']['connection']['port'] = $params['DATABASE']['PORT'];
284
285 1
            if ($params['DATABASE']['DRIVER'] === 'pdo_mysql') {
286 1
                $config['entity_manager']['connection']['driverOptions'] = [
287 1
                    \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
288
                ];
289 1
            }
290
        }
291
292 1
        return $config;
293
    }
294
295 1
    protected function createDatabase()
296
    {
297 1
        $this->output->writeln('Initializing database...');
298 1
        return $this->runCommand('php vendor/bin/doctrine.php orm:schema-tool:create', 'Error generating database.');
299
    }
300
301
    /**
302
     * @param string $command
303
     * @param string $errorMessage
304
     * @return bool
305
     */
306 1
    protected function runCommand($command, $errorMessage)
307
    {
308 1
        $process = $this->processHelper->run($this->output, $command);
309 1
        if ($process->isSuccessful()) {
310 1
            $this->output->writeln('    <info>Success!</info>');
311 1
            return true;
312
        } else {
313
            if ($this->output->isVerbose()) {
314
                return false;
315
            }
316
            $this->output->writeln(
317
                '    <error>' . $errorMessage . '</error>  Run this command with -vvv to see specific error info.'
318
            );
319
            return false;
320
        }
321
    }
322
}
323