InitCommand   B
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 11
dl 0
loc 341
rs 7.4757
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 10 1
C initialize() 0 46 9
B execute() 0 15 5
B copyFiles() 0 25 4
A initializeBitrix() 0 10 2
A configureLicenseKey() 0 9 2
C configureModules() 0 52 9
A configureSettings() 0 8 2
D configureCluster() 0 41 9
D configureOptions() 0 26 9
A getType() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like InitCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InitCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * For the full copyright and license information, please view the LICENSE.md
4
 * file that was distributed with this source code.
5
 */
6
7
namespace Notamedia\ConsoleJedi\Environment\Command;
8
9
use Bitrix\Main\Application;
10
use Bitrix\Main\Config\Configuration;
11
use Bitrix\Main\Config\Option;
12
use Bitrix\Main\IO\File;
13
use Bitrix\Main\Loader;
14
use Notamedia\ConsoleJedi\Application\Command\Command;
15
use Notamedia\ConsoleJedi\Application\Exception\BitrixException;
16
use Notamedia\ConsoleJedi\Module\Command\ModuleCommand;
17
use Notamedia\ConsoleJedi\Module\Exception\ModuleException;
18
use Notamedia\ConsoleJedi\Module\Exception\ModuleInstallException;
19
use Notamedia\ConsoleJedi\Module\Module;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Question\ChoiceQuestion;
24
use Symfony\Component\Console\Helper\ProgressBar;
25
use Symfony\Component\Filesystem\Filesystem;
26
27
/**
28
 * Installation environment settings.
29
 *
30
 * @author Nik Samokhvalov <[email protected]>
31
 */
32
class InitCommand extends Command
33
{
34
    /**
35
     * @var string Path to the environment directory.
36
     */
37
    protected $dir;
38
    /**
39
     * @var array Settings for current environment. The contents of the file `config.php`.
40
     */
41
    protected $config = [];
42
    /**
43
     * @var array Methods which be running in the first place.
44
     */
45
    protected $bootstrap = ['copyFiles', 'initializeBitrix'];
46
    /**
47
     * @var array Files that do not need to copy to the application when initializing the environment settings.
48
     */
49
    protected $excludedFiles = ['config.php'];
50
    /**
51
     * @var string Type of the initialized environment.
52
     */
53
    private $type;
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    protected function configure()
59
    {
60
        parent::configure();
61
62
        $this->setName('env:init')
63
            ->setDescription('Installation environment settings')
64
            ->setHelp('Run command and select environment from the list')
65
            ->addArgument('type', InputArgument::OPTIONAL, 'Type of the environments')
66
            ->addOption('memcache-cold-start', null, null, 'All memcache servers adds with status "ready"');
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    protected function initialize(InputInterface $input, OutputInterface $output)
73
    {
74
        parent::initialize($input, $output);
75
76
        if (!$this->getApplication()->getConfiguration()['env-dir']) {
77
            throw new \Exception('Config env-dir is missing');
78
        }
79
80
        $dir = $this->getApplication()->getRoot() . '/' . $this->getApplication()->getConfiguration()['env-dir'];
81
82
        if (!is_dir($dir)) {
83
            throw new \Exception('Directory ' . $dir . ' is missing');
84
        }
85
86
        $environments = include $dir . '/index.php';
87
88
        if (!is_array($environments)) {
89
            throw new \Exception('File with description of environments is missing');
90
        } elseif (count($environments) == 0) {
91
            throw new \Exception('Environments not found in description file');
92
        }
93
94
        if ($input->getArgument('type')) {
95
            $type = $input->getArgument('type');
96
97
            if (!isset($environments[$type])) {
98
                throw new \Exception('Invalid environment code!');
99
            }
100
        } else {
101
            foreach ($environments as $type => $environment) {
102
                $choices[$type] = $environment['name'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$choices was never initialized. Although not strictly required by PHP, it is generally a good practice to add $choices = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
103
            }
104
105
            $questionHelper = $this->getHelper('question');
106
            $question = new ChoiceQuestion('<info>Which environment install?</info>', $choices, false);
0 ignored issues
show
Bug introduced by
The variable $choices does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
107
            $type = $questionHelper->ask($input, $output, $question);
108
        }
109
110
        if (!isset($environments[$type]['path'])) {
111
            throw new \Exception('Environment path not found!');
112
        }
113
114
        $this->type = $type;
115
        $this->dir = $dir . '/' . $environments[$type]['path'];
116
        $this->config = include $this->dir . '/config.php';
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    protected function execute(InputInterface $input, OutputInterface $output)
123
    {
124
        foreach ($this->bootstrap as $method) {
125
            $this->$method($input, $output);
126
        }
127
128
        foreach ($this->config as $config => $settings) {
129
            $method = 'configure' . ucfirst($config);
130
131
            if (!in_array($method, $this->bootstrap) && method_exists($this, $method)) {
132
                $output->writeln('<comment>Setup "' . $config . '"</comment>');
133
                $this->$method($input, $output, $settings);
134
            }
135
        }
136
    }
137
138
    /**
139
     * Copy files and directories from the environment directory to application.
140
     *
141
     * @param InputInterface $input
142
     * @param OutputInterface $output
143
     */
144
    protected function copyFiles(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    {
146
        $output->writeln('<comment>Copy files from the environment directory</comment>');
147
148
        $fs = new Filesystem();
149
150
        $directoryIterator = new \RecursiveDirectoryIterator($this->dir, \RecursiveDirectoryIterator::SKIP_DOTS);
151
        $iterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST);
152
153
        foreach ($iterator as $item) {
154
            if (in_array($iterator->getSubPathName(), $this->excludedFiles)) {
155
                continue;
156
            }
157
158
            $itemPath = $this->getApplication()->getRoot() . '/' . $iterator->getSubPathName();
159
160
            if ($item->isDir()) {
161
                $fs->mkdir($itemPath);
162
            } else {
163
                $fs->copy($item, $itemPath, true);
164
            }
165
166
            $output->writeln('   ' . $itemPath);
167
        }
168
    }
169
170
    protected function initializeBitrix()
171
    {
172
        $this->getApplication()->initializeBitrix();
173
174
        $connections = Configuration::getValue('connections');
175
176
        foreach ($connections as $name => $parameters) {
177
            Application::getInstance()->getConnectionPool()->cloneConnection($name, $name, $parameters);
178
        }
179
    }
180
181
    /**
182
     * Sets license key Bitrix CMS.
183
     *
184
     * @param InputInterface $input
185
     * @param OutputInterface $output
186
     * @param string $licenseKey
187
     */
188
    protected function configureLicenseKey(InputInterface $input, OutputInterface $output, $licenseKey)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $output is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
189
    {
190
        if (!is_string($licenseKey)) {
191
            throw new \InvalidArgumentException('Config "licenseKey" must be string type.');
192
        }
193
194
        $licenseFileContent = "<" . "? $" . "LICENSE_KEY = \"" . EscapePHPString($licenseKey) . "\"; ?" . ">";
195
        File::putFileContents(Application::getDocumentRoot() . BX_ROOT . '/license_key.php', $licenseFileContent);
196
    }
197
198
    /**
199
     * Installation modules.
200
     *
201
     * @param InputInterface $input
202
     * @param OutputInterface $output
203
     * @param array $modules
204
     * @throws BitrixException
205
     * @throws \LogicException
206
     * @throws ModuleException
207
     */
208
    protected function configureModules(InputInterface $input, OutputInterface $output, array $modules)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
209
    {
210
        $app = $this->getApplication();
211
        if ($app->getConfiguration()) {
212
            $app->addCommands(ModuleCommand::getCommands());
213
            if ($app->getBitrixStatus() != \Notamedia\ConsoleJedi\Application\Application::BITRIX_STATUS_COMPLETE) {
214
                throw new BitrixException('Bitrix core is not available');
215
            }
216
        } else {
217
            throw new BitrixException('No configuration loaded');
218
        }
219
220
        if (!is_array($modules)) {
221
            throw new \LogicException('Incorrect modules configuration');
222
        }
223
224
        if (!count($modules)) {
225
            return;
226
        }
227
228
        $bar = new ProgressBar($output, count($modules));
229
        $bar->setRedrawFrequency(1);
230
        $bar->setFormat('verbose');
231
232
        foreach ($modules as $moduleName) {
233
            $message = "\r" . '   module:load ' . $moduleName . ': ';
234
235
            try {
236
                if (isset($bar)) {
237
                    $bar->setMessage($message);
238
                    $bar->display();
239
                }
240
241
                (new Module($moduleName))->load()->register();
242
243
                $bar->clear();
244
                $output->writeln($message . '<info>ok</info>');
245
            } catch (ModuleInstallException $e) {
246
                $bar->clear();
247
                $output->writeln($e->getMessage(), OutputInterface::VERBOSITY_VERBOSE);
248
                $output->writeln($message . '<comment>not registered</comment> (install it in admin panel)');
249
            } catch (ModuleException $e) {
250
                $bar->clear();
251
                $output->writeln($e->getMessage(), OutputInterface::VERBOSITY_VERBOSE);
252
                $output->writeln($message . '<error>FAILED</error>');
253
            }
254
            $bar->advance();
255
        }
256
        $bar->finish();
257
        $bar->clear();
258
        $output->write("\r");
259
    }
260
261
    /**
262
     * Sets configs to .settings.php.
263
     *
264
     * @param InputInterface $input
265
     * @param OutputInterface $output
266
     * @param array $settings
267
     */
268
    protected function configureSettings(InputInterface $input, OutputInterface $output, array $settings)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $output is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
269
    {
270
        $configuration = Configuration::getInstance();
271
272
        foreach ($settings as $name => $value) {
273
            $configuration->setValue($name, $value);
274
        }
275
    }
276
277
    /**
278
     * Installation config to module "cluster".
279
     *
280
     * @param InputInterface $input
281
     * @param OutputInterface $output
282
     * @param array $cluster
283
     *
284
     * @throws \Bitrix\Main\LoaderException
285
     * @throws \Exception
286
     */
287
    protected function configureCluster(InputInterface $input, OutputInterface $output, array $cluster)
288
    {
289
        global $APPLICATION;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
290
291
        if (!Loader::includeModule('cluster')) {
292
            throw new \Exception('Failed to load module "cluster"');
293
        }
294
295
        $memcache = new \CClusterMemcache;
296
297
        if (isset($cluster['memcache'])) {
298
            $output->writeln('   <comment>memcache</comment>');
299
300
            if (!is_array($cluster['memcache'])) {
301
                throw new \Exception('Server info must be an array');
302
            }
303
304
            $rsServers = $memcache->GetList();
305
306
            while ($server = $rsServers->Fetch()) {
307
                $memcache->Delete($server['ID']);
308
            }
309
310
            foreach ($cluster['memcache'] as $index => $server) {
311
                $serverId = $memcache->Add($server);
312
313
                if ($serverId && !$input->getOption('memcache-cold-start')) {
314
                    $memcache->Resume($serverId);
315
                } else {
316
                    $exception = $APPLICATION->GetException();
317
                    $message = 'Invalid memcache config with index ' . $index;
318
319
                    if ($exception->GetString()) {
320
                        $message = str_replace('<br>', "\n", $exception->GetString());
321
                    }
322
323
                    $output->writeln('<error>' . $message . '</error>');
324
                }
325
            }
326
        }
327
    }
328
329
    /**
330
     * Installation of option modules.
331
     *
332
     * @param InputInterface $input
333
     * @param OutputInterface $output
334
     * @param array $options
335
     */
336
    protected function configureOptions(InputInterface $input, OutputInterface $output, array $options)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
337
    {
338
        if (empty($options)) {
339
            return;
340
        }
341
342
        foreach ($options as $module => $moduleOptions) {
343
            if (!is_array($moduleOptions) || empty($moduleOptions)) {
344
                continue;
345
            }
346
347
            foreach ($moduleOptions as $code => $value) {
348
                if (is_array($value)) {
349
                    if (isset($value['value']) && isset($value['siteId'])) {
350
                        Option::set($module, $code, $value['value'], $value['siteId']);
351
                    } else {
352
                        $output->writeln('<error>Invalid option for module "' . $module . '" with code "' . $code . '"</error>');
353
                    }
354
                } else {
355
                    Option::set($module, $code, $value);
356
357
                    $output->writeln('   option: "' . $code . '", module: "' . $module . '"');
358
                }
359
            }
360
        }
361
    }
362
363
    /**
364
     * Gets type of initialized environment.
365
     * 
366
     * @return string
367
     */
368
    public function getType()
369
    {
370
        return $this->type;
371
    }
372
}
373