Completed
Push — master ( d2354a...84b175 )
by Nik
02:25
created

InitCommand::configureCluster()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 41
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 4.909
c 0
b 0
f 0
cc 9
eloc 22
nc 11
nop 3
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
    /**
52
     * {@inheritdoc}
53
     */
54
    protected function configure()
55
    {
56
        parent::configure();
57
58
        $this->setName('env:init')
59
            ->setDescription('Installation environment settings')
60
            ->setHelp('Run command and select environment from the list')
61
            ->addArgument('type', InputArgument::OPTIONAL, 'Type of the environments')
62
            ->addOption('memcache-cold-start', null, null, 'All memcache servers adds with status "ready"');
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    protected function initialize(InputInterface $input, OutputInterface $output)
69
    {
70
        parent::initialize($input, $output);
71
72
        if (!$this->getApplication()->getConfiguration()['env-dir']) {
73
            throw new \Exception('Config env-dir is missing');
74
        }
75
76
        $dir = $this->getApplication()->getRoot() . '/' . $this->getApplication()->getConfiguration()['env-dir'];
77
78
        if (!is_dir($dir)) {
79
            throw new \Exception('Directory ' . $dir . ' is missing');
80
        }
81
82
        $environments = include $dir . '/index.php';
83
84
        if (!is_array($environments)) {
85
            throw new \Exception('File with description of environments is missing');
86
        } elseif (count($environments) == 0) {
87
            throw new \Exception('Environments not found in description file');
88
        }
89
90
        if ($input->getArgument('type')) {
91
            $code = $input->getArgument('type');
92
93
            if (!isset($environments[$code])) {
94
                throw new \Exception('Invalid environment code!');
95
            }
96
        } else {
97
            foreach ($environments as $code => $environment) {
98
                $choices[$code] = $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...
99
            }
100
101
            $questionHelper = $this->getHelper('question');
102
            $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...
103
            $code = $questionHelper->ask($input, $output, $question);
104
        }
105
106
        if (!isset($environments[$code]['path'])) {
107
            throw new \Exception('Environment path not found!');
108
        }
109
110
        $this->dir = $dir . '/' . $environments[$code]['path'];
111
        $this->config = include $this->dir . '/config.php';
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    protected function execute(InputInterface $input, OutputInterface $output)
118
    {
119
        foreach ($this->bootstrap as $method) {
120
            $this->$method($input, $output);
121
        }
122
123
        foreach ($this->config as $config => $settings) {
124
            $method = 'configure' . ucfirst($config);
125
126
            if (!in_array($method, $this->bootstrap) && method_exists($this, $method)) {
127
                $output->writeln('<comment>Setup "' . $config . '"</comment>');
128
                $this->$method($input, $output, $settings);
129
            }
130
        }
131
    }
132
133
    /**
134
     * Copy files and directories from the environment directory to application.
135
     *
136
     * @param InputInterface $input
137
     * @param OutputInterface $output
138
     */
139
    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...
140
    {
141
        $output->writeln('<comment>Copy files from the environment directory</comment>');
142
143
        $fs = new Filesystem();
144
145
        $directoryIterator = new \RecursiveDirectoryIterator($this->dir, \RecursiveDirectoryIterator::SKIP_DOTS);
146
        $iterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST);
147
148
        foreach ($iterator as $item) {
149
            if (in_array($iterator->getSubPathName(), $this->excludedFiles)) {
150
                continue;
151
            }
152
153
            $itemPath = $this->getApplication()->getRoot() . '/' . $iterator->getSubPathName();
154
155
            if ($item->isDir()) {
156
                $fs->mkdir($itemPath);
157
            } else {
158
                $fs->copy($item, $itemPath, true);
159
            }
160
161
            $output->writeln('   ' . $itemPath);
162
        }
163
    }
164
165
    protected function initializeBitrix()
166
    {
167
        $this->getApplication()->initializeBitrix();
168
169
        $connections = Configuration::getValue('connections');
170
171
        foreach ($connections as $name => $parameters) {
172
            Application::getInstance()->getConnectionPool()->cloneConnection($name, $name, $parameters);
173
        }
174
    }
175
176
    /**
177
     * Sets license key Bitrix CMS.
178
     *
179
     * @param InputInterface $input
180
     * @param OutputInterface $output
181
     * @param string $licenseKey
182
     */
183
    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...
184
    {
185
        if (!is_string($licenseKey)) {
186
            throw new \InvalidArgumentException('Config "licenseKey" must be string type.');
187
        }
188
189
        $licenseFileContent = "<" . "? $" . "LICENSE_KEY = \"" . EscapePHPString($licenseKey) . "\"; ?" . ">";
190
        File::putFileContents(Application::getDocumentRoot() . BX_ROOT . '/license_key.php', $licenseFileContent);
191
    }
192
193
    /**
194
     * Installation modules.
195
     *
196
     * @param InputInterface $input
197
     * @param OutputInterface $output
198
     * @param array $modules
199
     * @throws BitrixException
200
     * @throws \LogicException
201
     * @throws ModuleException
202
     */
203
    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...
204
    {
205
        $app = $this->getApplication();
206
        if ($app->getConfiguration()) {
207
            $app->addCommands(ModuleCommand::getCommands());
208
            if ($app->getBitrixStatus() != \Notamedia\ConsoleJedi\Application\Application::BITRIX_STATUS_COMPLETE) {
209
                throw new BitrixException('Bitrix core is not available');
210
            }
211
        } else {
212
            throw new BitrixException('No configuration loaded');
213
        }
214
215
        if (!is_array($modules)) {
216
            throw new \LogicException('Incorrect modules configuration');
217
        }
218
219
        if (!count($modules)) {
220
            return;
221
        }
222
223
        $bar = new ProgressBar($output, count($modules));
224
        $bar->setRedrawFrequency(1);
225
        $bar->setFormat('verbose');
226
227
        foreach ($modules as $moduleName) {
228
            $message = "\r" . '   module:load ' . $moduleName . ': ';
229
230
            try {
231
                if (isset($bar)) {
232
                    $bar->setMessage($message);
233
                    $bar->display();
234
                }
235
236
                (new Module($moduleName))->load()->register();
237
238
                $bar->clear();
239
                $output->writeln($message . '<info>ok</info>');
240
            } catch (ModuleInstallException $e) {
241
                $bar->clear();
242
                $output->writeln($e->getMessage(), OutputInterface::VERBOSITY_VERBOSE);
243
                $output->writeln($message . '<comment>not registered</comment> (install it in admin panel)');
244
            } catch (ModuleException $e) {
245
                $bar->clear();
246
                $output->writeln($e->getMessage(), OutputInterface::VERBOSITY_VERBOSE);
247
                $output->writeln($message . '<error>FAILED</error>');
248
            }
249
            $bar->advance();
250
        }
251
        $bar->finish();
252
        $bar->clear();
253
        $output->write("\r");
254
    }
255
256
    /**
257
     * Sets configs to .settings.php.
258
     *
259
     * @param InputInterface $input
260
     * @param OutputInterface $output
261
     * @param array $settings
262
     */
263
    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...
264
    {
265
        $configuration = Configuration::getInstance();
266
267
        foreach ($settings as $name => $value) {
268
            $configuration->setValue($name, $value);
269
        }
270
    }
271
272
    /**
273
     * Installation config to module "cluster".
274
     *
275
     * @param InputInterface $input
276
     * @param OutputInterface $output
277
     * @param array $cluster
278
     *
279
     * @throws \Bitrix\Main\LoaderException
280
     * @throws \Exception
281
     */
282
    protected function configureCluster(InputInterface $input, OutputInterface $output, array $cluster)
283
    {
284
        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...
285
286
        if (!Loader::includeModule('cluster')) {
287
            throw new \Exception('Failed to load module "cluster"');
288
        }
289
290
        $memcache = new \CClusterMemcache;
291
292
        if (isset($cluster['memcache'])) {
293
            $output->writeln('   <comment>memcache</comment>');
294
295
            if (!is_array($cluster['memcache'])) {
296
                throw new \Exception('Server info must be an array');
297
            }
298
299
            $rsServers = $memcache->GetList();
300
301
            while ($server = $rsServers->Fetch()) {
302
                $memcache->Delete($server['ID']);
303
            }
304
305
            foreach ($cluster['memcache'] as $index => $server) {
306
                $serverId = $memcache->Add($server);
307
308
                if ($serverId && !$input->getOption('memcache-cold-start')) {
309
                    $memcache->Resume($serverId);
310
                } else {
311
                    $exception = $APPLICATION->GetException();
312
                    $message = 'Invalid memcache config with index ' . $index;
313
314
                    if ($exception->GetString()) {
315
                        $message = str_replace('<br>', "\n", $exception->GetString());
316
                    }
317
318
                    $output->writeln('<error>' . $message . '</error>');
319
                }
320
            }
321
        }
322
    }
323
324
    /**
325
     * Installation of option modules.
326
     *
327
     * @param InputInterface $input
328
     * @param OutputInterface $output
329
     * @param array $options
330
     */
331
    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...
332
    {
333
        if (empty($options)) {
334
            return;
335
        }
336
337
        foreach ($options as $module => $moduleOptions) {
338
            if (!is_array($moduleOptions) || empty($moduleOptions)) {
339
                continue;
340
            }
341
342
            foreach ($moduleOptions as $code => $value) {
343
                if (is_array($value)) {
344
                    if (isset($value['value']) && isset($value['siteId'])) {
345
                        Option::set($module, $code, $value['value'], $value['siteId']);
346
                    } else {
347
                        $output->writeln('<error>Invalid option for module "' . $module . '" with code "' . $code . '"</error>');
348
                    }
349
                } else {
350
                    Option::set($module, $code, $value);
351
352
                    $output->writeln('   option: "' . $code . '", module: "' . $module . '"');
353
                }
354
            }
355
        }
356
    }
357
}
358