Completed
Pull Request — develop (#891)
by Luke
03:50
created

AbstractMagentoCommand::_getHelper()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
3
namespace N98\Magento\Command;
4
5
use Composer\Factory as ComposerFactory;
6
use Composer\IO\ConsoleIO;
7
use Composer\Package\Loader\ArrayLoader as PackageLoader;
8
use Composer\Package\PackageInterface;
9
use InvalidArgumentException;
10
use N98\Util\Console\Helper\MagentoHelper;
11
use N98\Util\OperatingSystem;
12
use N98\Util\StringTyped;
13
use RuntimeException;
14
use Symfony\Component\Console\Command\Command;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
18
/**
19
 * Class AbstractMagentoCommand
20
 *
21
 * @package N98\Magento\Command
22
 *
23
 * @method \N98\Magento\Application getApplication() getApplication()
24
 */
25
abstract class AbstractMagentoCommand extends Command
26
{
27
    /**
28
     * @var int
29
     */
30
    const MAGENTO_MAJOR_VERSION_1 = 1;
31
32
    /**
33
     * @var int
34
     */
35
    const MAGENTO_MAJOR_VERSION_2 = 2;
36
37
    /**
38
     * @var string
39
     */
40
    protected $_magentoRootFolder = null;
41
42
    /**
43
     * @var int
44
     */
45
    protected $_magentoMajorVersion = self::MAGENTO_MAJOR_VERSION_1;
46
47
    /**
48
     * @var bool
49
     */
50
    protected $_magentoEnterprise = false;
51
52
    /**
53
     * @var array
54
     */
55
    protected $_deprecatedAlias = array();
56
57
    /**
58
     * @var array
59
     */
60
    protected $_websiteCodeMap = array();
61
62
    /**
63
     * Initializes the command just after the input has been validated.
64
     *
65
     * This is mainly useful when a lot of commands extends one main command
66
     * where some things need to be initialized based on the input arguments and options.
67
     *
68
     * @param InputInterface  $input  An InputInterface instance
69
     * @param OutputInterface $output An OutputInterface instance
70
     */
71
    protected function initialize(InputInterface $input, OutputInterface $output)
72
    {
73
        $this->checkDeprecatedAliases($input, $output);
74
    }
75
76
    private function _initWebsites()
77
    {
78
        $this->_websiteCodeMap = array();
79
        /** @var \Mage_Core_Model_Website[] $websites */
80
        $websites = \Mage::app()->getWebsites(false);
81
        foreach ($websites as $website) {
82
            $this->_websiteCodeMap[$website->getId()] = $website->getCode();
83
        }
84
    }
85
86
    /**
87
     * @param int $websiteId
88
     * @return string
89
     */
90
    protected function _getWebsiteCodeById($websiteId)
91
    {
92
        if (empty($this->_websiteCodeMap)) {
93
            $this->_initWebsites();
94
        }
95
96
        if (isset($this->_websiteCodeMap[$websiteId])) {
97
            return $this->_websiteCodeMap[$websiteId];
98
        }
99
100
        return '';
101
    }
102
103
    /**
104
     * @param string $websiteCode
105
     * @return int
106
     */
107
    protected function _getWebsiteIdByCode($websiteCode)
108
    {
109
        if (empty($this->_websiteCodeMap)) {
110
            $this->_initWebsites();
111
        }
112
        $websiteMap = array_flip($this->_websiteCodeMap);
113
114
        return $websiteMap[$websiteCode];
115
    }
116
117
    /**
118
     * @param string|null $commandClass
119
     * @return array
120
     */
121
    protected function getCommandConfig($commandClass = null)
122
    {
123
        if (null === $commandClass) {
124
            $commandClass = get_class($this);
125
        }
126
127
        /** @var \N98\Magento\Application $application */
128
        $application = $this->getApplication();
129
        return (array) $application->getConfig('commands', $commandClass);
130
    }
131
132
    /**
133
     * @param OutputInterface $output
134
     * @param string $text
135
     * @param string $style
136
     */
137
    protected function writeSection(OutputInterface $output, $text, $style = 'bg=blue;fg=white')
138
    {
139
        $output->writeln(array(
140
            '',
141
            $this->getHelper('formatter')->formatBlock($text, $style, true),
142
            '',
143
        ));
144
    }
145
146
    /**
147
     * Bootstrap magento shop
148
     *
149
     * @param bool $soft
150
     * @return bool
151
     */
152
    protected function initMagento($soft = false)
153
    {
154
        $application = $this->getApplication();
155
        $init = $application->initMagento($soft);
156
        if ($init) {
157
            $this->_magentoRootFolder = $application->getMagentoRootFolder();
158
        }
159
160
        return $init;
161
    }
162
163
    /**
164
     * Search for magento root folder
165
     *
166
     * @param OutputInterface $output
167
     * @param bool $silent print debug messages
168
     * @throws RuntimeException
169
     */
170
    public function detectMagento(OutputInterface $output, $silent = true)
171
    {
172
        $this->getApplication()->detectMagento();
173
174
        $this->_magentoEnterprise = $this->getApplication()->isMagentoEnterprise();
175
        $this->_magentoRootFolder = $this->getApplication()->getMagentoRootFolder();
176
        $this->_magentoMajorVersion = $this->getApplication()->getMagentoMajorVersion();
177
178
        if (!$silent) {
179
            $editionString = ($this->_magentoEnterprise ? ' (Enterprise Edition) ' : '');
180
            $output->writeln(
181
                '<info>Found Magento ' . $editionString . 'in folder "' . $this->_magentoRootFolder . '"</info>'
182
            );
183
        }
184
185
        if (!empty($this->_magentoRootFolder)) {
186
            return;
187
        }
188
189
        throw new RuntimeException('Magento folder could not be detected');
190
    }
191
192
    /**
193
     * Die if not Enterprise
194
     */
195
    protected function requireEnterprise(OutputInterface $output)
196
    {
197
        if (!$this->_magentoEnterprise) {
198
            $output->writeln('<error>Enterprise Edition is required but was not detected</error>');
199
            exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method requireEnterprise() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
200
        }
201
    }
202
203
    /**
204
     * @return \Mage_Core_Helper_Data
205
     */
206
    protected function getCoreHelper()
207
    {
208
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_2) {
209
            return \Mage::helper('Mage_Core_Helper_Data');
210
        }
211
        return \Mage::helper('core');
212
    }
213
214
    /**
215
     * @param InputInterface $input
216
     * @param OutputInterface $output
217
     * @return \Composer\Downloader\DownloadManager
218
     */
219
    protected function getComposerDownloadManager($input, $output)
220
    {
221
        return $this->getComposer($input, $output)->getDownloadManager();
222
    }
223
224
    /**
225
     * @param array|PackageInterface $config
226
     * @return \Composer\Package\CompletePackage
227
     */
228
    protected function createComposerPackageByConfig($config)
229
    {
230
        $packageLoader = new PackageLoader();
231
        return $packageLoader->load($config);
0 ignored issues
show
Bug introduced by
It seems like $config defined by parameter $config on line 228 can also be of type object<Composer\Package\PackageInterface>; however, Composer\Package\Loader\ArrayLoader::load() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
232
    }
233
234
    /**
235
     * @param InputInterface $input
236
     * @param OutputInterface $output
237
     * @param array|PackageInterface $config
238
     * @param string $targetFolder
239
     * @param bool $preferSource
240
     * @return \Composer\Package\CompletePackage
241
     */
242
    protected function downloadByComposerConfig(
243
        InputInterface $input,
244
        OutputInterface $output,
245
        $config,
246
        $targetFolder,
247
        $preferSource = true
248
    ) {
249
        $dm = $this->getComposerDownloadManager($input, $output);
250
        if (!$config instanceof PackageInterface) {
251
            $package = $this->createComposerPackageByConfig($config);
252
        } else {
253
            $package = $config;
254
        }
255
256
        $helper = new \N98\Util\Console\Helper\MagentoHelper();
257
        $helper->detect($targetFolder);
258
        if ($this->isSourceTypeRepository($package->getSourceType()) && $helper->getRootFolder() == $targetFolder) {
259
            $package->setInstallationSource('source');
260
            $this->checkRepository($package, $targetFolder);
261
            $dm->update($package, $package, $targetFolder);
262
        } else {
263
            $dm->download($package, $targetFolder, $preferSource);
264
        }
265
266
        return $package;
267
    }
268
269
    /**
270
     * brings locally cached repository up to date if it is missing the requested tag
271
     *
272
     * @param PackageInterface $package
273
     * @param string $targetFolder
274
     */
275
    protected function checkRepository($package, $targetFolder)
276
    {
277
        if ($package->getSourceType() == 'git') {
278
            $command = sprintf(
279
                'cd %s && git rev-parse refs/tags/%s',
280
                escapeshellarg($this->normalizePath($targetFolder)),
281
                escapeshellarg($package->getSourceReference())
282
            );
283
            $existingTags = shell_exec($command);
284
            if (!$existingTags) {
285
                $command = sprintf('cd %s && git fetch', escapeshellarg($this->normalizePath($targetFolder)));
286
                shell_exec($command);
287
            }
288
        } elseif ($package->getSourceType() == 'hg') {
289
            $command = sprintf(
290
                'cd %s && hg log --template "{tags}" -r %s',
291
                escapeshellarg($targetFolder),
292
                escapeshellarg($package->getSourceReference())
293
            );
294
            $existingTag = shell_exec($command);
295
            if ($existingTag === $package->getSourceReference()) {
296
                $command = sprintf('cd %s && hg pull', escapeshellarg($targetFolder));
297
                shell_exec($command);
298
            }
299
        }
300
    }
301
302
    /**
303
     * normalize paths on windows / cygwin / msysgit
304
     *
305
     * when using a path value that has been created in a cygwin shell but then PHP uses it inside a cmd shell it needs
306
     * to be filtered.
307
     *
308
     * @param  string $path
309
     * @return string
310
     */
311
    protected function normalizePath($path)
312
    {
313
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
314
            $path = strtr($path, '/', '\\');
315
        }
316
        return $path;
317
    }
318
319
    /**
320
     * obtain composer
321
     *
322
     * @param InputInterface  $input
323
     * @param OutputInterface $output
324
     *
325
     * @return \Composer\Composer
326
     */
327
    protected function getComposer(InputInterface $input, OutputInterface $output)
328
    {
329
        $io = new ConsoleIO($input, $output, $this->getHelperSet());
0 ignored issues
show
Bug introduced by
It seems like $this->getHelperSet() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
330
        $config = array(
331
            'config' => array(
332
                'secure-http' => false,
333
            ),
334
        );
335
336
        return ComposerFactory::create($io, $config);
337
    }
338
339
    /**
340
     * @param string $alias
341
     * @param string $message
342
     * @return AbstractMagentoCommand
343
     */
344
    protected function addDeprecatedAlias($alias, $message)
345
    {
346
        $this->_deprecatedAlias[$alias] = $message;
347
348
        return $this;
349
    }
350
351
    /**
352
     * @param InputInterface $input
353
     * @param OutputInterface $output
354
     */
355
    protected function checkDeprecatedAliases(InputInterface $input, OutputInterface $output)
356
    {
357
        if (isset($this->_deprecatedAlias[$input->getArgument('command')])) {
358
            $output->writeln(
359
                '<error>Deprecated:</error> <comment>' . $this->_deprecatedAlias[$input->getArgument('command')] .
360
                '</comment>'
361
            );
362
        }
363
    }
364
365
    /**
366
     * Magento 1 / 2 switches
367
     *
368
     * @param string $mage1code Magento 1 class code
369
     * @param string $mage2class Magento 2 class name
370
     * @return \Mage_Core_Model_Abstract
371
     */
372
    protected function _getModel($mage1code, $mage2class)
373
    {
374
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_2) {
375
            return \Mage::getModel($mage2class);
376
        } else {
377
            return \Mage::getModel($mage1code);
378
        }
379
    }
380
381
    /**
382
     * Magento 1 / 2 switches
383
     *
384
     * @param string $mage1code Magento 1 class code
385
     * @param string $mage2class Magento 2 class name
386
     * @return \Mage_Core_Helper_Abstract
387
     */
388
    protected function _getHelper($mage1code, $mage2class)
389
    {
390
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_2) {
391
            return \Mage::helper($mage2class);
392
        } else {
393
            return \Mage::helper($mage1code);
394
        }
395
    }
396
397
    /**
398
     * Magento 1 / 2 switches
399
     *
400
     * @param string $mage1code Magento 1 class code
401
     * @param string $mage2class Magento 2 class name
402
     * @return \Mage_Core_Model_Abstract
403
     */
404
    protected function _getSingleton($mage1code, $mage2class)
405
    {
406
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_2) {
407
            return \Mage::getModel($mage2class);
408
        } else {
409
            return \Mage::getModel($mage1code);
410
        }
411
    }
412
413
    /**
414
     * Magento 1 / 2 switches
415
     *
416
     * @param string $mage1code Magento 1 class code
417
     * @param string $mage2class Magento 2 class name
418
     * @return \Mage_Core_Model_Abstract
419
     */
420
    protected function _getResourceModel($mage1code, $mage2class)
421
    {
422
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_2) {
423
            return \Mage::getResourceModel($mage2class);
424
        } else {
425
            return \Mage::getResourceModel($mage1code);
426
        }
427
    }
428
429
    /**
430
     * Magento 1 / 2 switches
431
     *
432
     * @param string $mage1code Magento 1 class code
433
     * @param string $mage2class Magento 2 class name
434
     * @return \Mage_Core_Model_Abstract
435
     */
436
    protected function _getResourceSingleton($mage1code, $mage2class)
437
    {
438
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_2) {
439
            return \Mage::getResourceSingleton($mage2class);
440
        } else {
441
            return \Mage::getResourceSingleton($mage1code);
442
        }
443
    }
444
445
    /**
446
     * @param string $value
447
     * @return bool
448
     */
449
    protected function _parseBoolOption($value)
450
    {
451
        return StringTyped::parseBoolOption($value);
452
    }
453
454
    /**
455
     * @param string $value
456
     * @return string
457
     */
458
    protected function formatActive($value)
459
    {
460
        return StringTyped::formatActive($value);
461
    }
462
463
    /**
464
     * @param InputInterface  $input
465
     * @param OutputInterface $output
466
     *
467
     * @return int
468
     */
469
    public function run(InputInterface $input, OutputInterface $output)
470
    {
471
        $this->getHelperSet()->setCommand($this);
472
473
        return parent::run($input, $output);
474
    }
475
476
    /**
477
     * @param InputInterface $input
478
     * @param OutputInterface $output
479
     */
480
    protected function chooseInstallationFolder(InputInterface $input, OutputInterface $output)
481
    {
482
        /**
483
         * @param string $folderName
484
         *
485
         * @return string
486
         */
487
        $validateInstallationFolder = function ($folderName) use ($input) {
488
            $folderName = rtrim(trim($folderName, ' '), '/');
489
            // resolve folder-name to current working directory if relative
490
            if (substr($folderName, 0, 1) == '.') {
491
                $cwd = OperatingSystem::getCwd();
492
                $folderName = $cwd . substr($folderName, 1);
493
            }
494
495
            if (empty($folderName)) {
496
                throw new InvalidArgumentException('Installation folder cannot be empty');
497
            }
498
499
            if (!is_dir($folderName)) {
500
                if (!@mkdir($folderName, 0777, true)) {
501
                    throw new InvalidArgumentException('Cannot create folder.');
502
                }
503
504
                return $folderName;
505
            }
506
507
            if ($input->hasOption('noDownload') && $input->getOption('noDownload')) {
508
                /** @var MagentoHelper $magentoHelper */
509
                $magentoHelper = new MagentoHelper();
510
                $magentoHelper->detect($folderName);
511
                if ($magentoHelper->getRootFolder() !== $folderName) {
512
                    throw new InvalidArgumentException(
513
                        sprintf(
514
                            'Folder %s is not a Magento working copy.',
515
                            $folderName
516
                        )
517
                    );
518
                }
519
520
                $localXml = $folderName . '/app/etc/local.xml';
521
                if (file_exists($localXml)) {
522
                    throw new InvalidArgumentException(
523
                        sprintf(
524
                            'Magento working copy in %s seems already installed. Please remove %s and retry.',
525
                            $folderName,
526
                            $localXml
527
                        )
528
                    );
529
                }
530
            }
531
532
            return $folderName;
533
        };
534
535
        if (($installationFolder = $input->getOption('installationFolder')) == null) {
536
            $defaultFolder = './magento';
537
            $question[] = "<question>Enter installation folder:</question> [<comment>" . $defaultFolder . "</comment>]";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$question was never initialized. Although not strictly required by PHP, it is generally a good practice to add $question = 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...
538
539
            $installationFolder = $this->getHelper('dialog')->askAndValidate(
540
                $output,
541
                $question,
542
                $validateInstallationFolder,
543
                false,
544
                $defaultFolder
545
            );
546
        } else {
547
            // @Todo improve validation and bring it to 1 single function
548
            $installationFolder = $validateInstallationFolder($installationFolder);
549
        }
550
551
        $this->config['installationFolder'] = realpath($installationFolder);
0 ignored issues
show
Bug introduced by
The property config does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
552
        \chdir($this->config['installationFolder']);
553
    }
554
555
    /**
556
     * @param string $type
557
     *
558
     * @return bool
559
     */
560
    protected function isSourceTypeRepository($type)
561
    {
562
        return in_array($type, array('git', 'hg'));
563
    }
564
565
    /**
566
     * @param string $argument
567
     * @param InputInterface $input
568
     * @param OutputInterface $output
569
     * @param string $message
570
     * @return string
571
     */
572
    protected function getOrAskForArgument($argument, InputInterface $input, OutputInterface $output, $message = null)
573
    {
574
        $inputArgument = $input->getArgument($argument);
575
        if ($inputArgument === null) {
576
            $message = $this->getArgumentMessage($argument, $message);
577
578
            /** @var  $dialog  \Symfony\Component\Console\Helper\DialogHelper */
579
            $dialog = $this->getHelper('dialog');
580
            return $dialog->ask($output, $message);
581
        }
582
583
        return $inputArgument;
584
    }
585
586
    /**
587
     * @param array $entries zero-indexed array of entries (represented by strings) to select from
588
     * @param OutputInterface $output
589
     * @param string $question
590
     * @return mixed
591
     */
592
    protected function askForArrayEntry(array $entries, OutputInterface $output, $question)
593
    {
594
        $dialog = '';
595
        foreach ($entries as $key => $entry) {
596
            $dialog .= '<comment>[' . ($key + 1) . ']</comment> ' . $entry . "\n";
597
        }
598
        $dialog .= "<question>{$question}</question> ";
599
600
        $selected = $this->getHelper('dialog')->askAndValidate($output, $dialog, function ($typeInput) use ($entries) {
601
            if (!in_array($typeInput, range(1, count($entries)))) {
602
                throw new InvalidArgumentException('Invalid type');
603
            }
604
605
            return $typeInput;
606
        });
607
608
        return $entries[$selected - 1];
609
    }
610
611
    /**
612
     * @param string $argument
613
     * @param string $message [optional]
614
     * @return string
615
     */
616
    protected function getArgumentMessage($argument, $message = null)
617
    {
618
        if (null === $message) {
619
            $message = ucfirst($argument);
620
        }
621
622
        return sprintf('<question>%s:</question>', $message);
623
    }
624
}
625