Completed
Push — develop ( 72c1e4...69c1ec )
by Christian
05:49 queued 01:13
created

MetaCommand::writeToOutputV2017()   F

Complexity

Conditions 17
Paths 1001

Size

Total Lines 84

Duplication

Lines 12
Ratio 14.29 %

Importance

Changes 0
Metric Value
cc 17
nc 1001
nop 3
dl 12
loc 84
rs 1.189
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
3
namespace N98\Magento\Command\Developer\Ide\PhpStorm;
4
5
use Exception;
6
use N98\Magento\Command\AbstractMagentoCommand;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use Symfony\Component\Finder\Finder;
11
use Symfony\Component\Finder\SplFileInfo;
12
use UnexpectedValueException;
13
use Varien_Simplexml_Element;
14
15
class MetaCommand extends AbstractMagentoCommand
16
{
17
    /**
18
     * @var array
19
     */
20
    protected $groups = array(
21
        'blocks',
22
        'helpers',
23
        'models',
24
        'resource models',
25
        'resource helpers',
26
    );
27
28
    /**
29
     * List of supported static factory methods
30
     *
31
     * @var array
32
     */
33
    protected $groupFactories = array(
34
        'blocks' => array(
35
            '\Mage::getBlockSingleton',
36
        ),
37
        'helpers' => array(
38
            '\Mage::helper',
39
        ),
40
        'models' => array(
41
            '\Mage::getModel',
42
            '\Mage::getSingleton',
43
        ),
44
        'resource helpers' => array(
45
            '\Mage::getResourceHelper',
46
        ),
47
        'resource models' => array(
48
            '\Mage::getResourceModel',
49
            '\Mage::getResourceSingleton',
50
        ),
51
    );
52
53
    /**
54
     * List of supported helper methods
55
     *
56
     * @var array
57
     */
58
    protected $methodFactories = array(
59
        'blocks' => array(
60
            '\Mage_Core_Model_Layout::createBlock',
61
        ),
62
        'helpers' => array(
63
            '\Mage_Admin_Model_User::_getHelper',
64
            '\Mage_Adminhtml_Controller_Rss_Abstract::_getHelper',
65
            '\Mage_Api_Model_User::_getHelper',
66
            '\Mage_Core_Block_Abstract::helper',
67
            '\Mage_Core_Model_App::getHelper',
68
            '\Mage_Core_Model_Factory::getHelper',
69
            '\Mage_Core_Model_Layout::helper',
70
            '\Mage_Customer_AccountController::_getHelper',
71
            '\Mage_Customer_Model_Customer::_getHelper',
72
            '\Mage_ImportExport_Model_Import_Entity_Product::getHelper',
73
            '\Mage_Rss_Controller_Abstract::_getHelper',
74
            '\Mage_SalesRule_Model_Validator::_getHelper',
75
            '\Mage_Weee_Helper_Data::_getHelper',
76
            '\Mage_Weee_Model_Config_Source_Fpt_Tax::_getHelper',
77
        ),
78
    );
79
80
    /**
81
     * @var array
82
     */
83
    protected $missingHelperDefinitionModules = array(
84
        'Backup',
85
        'Bundle',
86
        'Captcha',
87
        'Catalog',
88
        'Centinel',
89
        'Checkout',
90
        'Cms',
91
        'Core',
92
        'Customer',
93
        'Dataflow',
94
        'Directory',
95
        'Downloadable',
96
        'Eav',
97
        'Index',
98
        'Install',
99
        'Log',
100
        'Media',
101
        'Newsletter',
102
        'Page',
103
        'Payment',
104
        'Paypal',
105
        'Persistent',
106
        'Poll',
107
        'Rating',
108
        'Reports',
109
        'Review',
110
        'Rss',
111
        'Rule',
112
        'Sales',
113
        'Shipping',
114
        'Sitemap',
115
        'Tag',
116
        'Tax',
117
        'Usa',
118
        'Weee',
119
        'Widget',
120
        'Wishlist',
121
    );
122
123
    const VERSION_OLD = 'old';
124
    const VERSION_2017 = '2016.2+';
125
126
    protected function configure()
127
    {
128
        $this
129
            ->setName('dev:ide:phpstorm:meta')
130
            ->addOption(
131
                'meta-version',
132
                null,
133
                InputOption::VALUE_REQUIRED,
134
                'PhpStorm Meta version (' . self::VERSION_OLD . ', ' . self::VERSION_2017 . ')',
135
                self::VERSION_2017
136
            )
137
            ->addOption('stdout', null, InputOption::VALUE_NONE, 'Print to stdout instead of file .phpstorm.meta.php')
138
            ->setDescription('Generates meta data file for PhpStorm auto completion (default version : ' . self::VERSION_2017 . ')');
139
    }
140
141
    /**
142
     * @param InputInterface  $input
143
     * @param OutputInterface $output
144
     *
145
     * @internal param string $package
146
     * @return void
147
     */
148
    protected function execute(InputInterface $input, OutputInterface $output)
149
    {
150
        $this->detectMagento($output);
151
        if (!$this->initMagento()) {
152
            return;
153
        }
154
155
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_1) {
156
            $classMaps = array();
157
158
            foreach ($this->groups as $group) {
159
                $classMaps[$group] = $this->getClassMapForGroup($group, $output);
160
161
                if (!$input->getOption('stdout') && count($classMaps[$group]) > 0) {
162
                    $output->writeln(
163
                        '<info>Generated definitions for <comment>' . $group . '</comment> group</info>'
164
                    );
165
                }
166
            }
167
168
            $version = $input->getOption('meta-version');
169
            if ($version == self::VERSION_OLD) {
170
                $this->writeToOutputOld($input, $output, $classMaps);
171
            } elseif ($version == self::VERSION_2017) {
172
                $this->writeToOutputV2017($input, $output, $classMaps);
173
            }
174
        } else {
175
            $output->write('Magento 2 is currently not supported');
176
        }
177
    }
178
179
    /**
180
     * @param SplFileInfo $file
181
     * @param string $classPrefix
182
     * @return string
183
     */
184
    protected function getRealClassname(SplFileInfo $file, $classPrefix)
185
    {
186
        $path = $file->getRelativePathname();
187
        if (substr($path, -4) !== '.php') {
188
            throw new UnexpectedValueException(
189
                sprintf('Expected that relative file %s ends with ".php"', var_export($path, true))
190
            );
191
        }
192
        $path = substr($path, 0, -4);
193
        $path = strtr($path, '\\', '/');
194
195
        return trim($classPrefix . '_' . strtr($path, '/', '_'), '_');
196
    }
197
198
    /**
199
     * @param SplFileInfo   $file
200
     * @param string        $classPrefix
201
     * @param string        $group
202
     * @return string
203
     */
204
    protected function getClassIdentifier(SplFileInfo $file, $classPrefix, $group = '')
205
    {
206
        $path = str_replace('.php', '', $file->getRelativePathname());
207
        $path = str_replace('\\', '/', $path);
208
        $parts = explode('/', $path);
209
        $parts = array_map('lcfirst', $parts);
210
        if ($path == 'Data' && ($group == 'helpers')) {
211
            array_pop($parts);
212
        }
213
214
        return rtrim($classPrefix . '/' . implode('_', $parts), '/');
215
    }
216
217
    /**
218
     * Verify whether given class is defined in given file because there is no sense in adding class with incorrect
219
     * file or path. Examples:
220
     * app/code/core/Mage/Core/Model/Mysql4/Design/Theme/Collection.php -> Mage_Core_Model_Mysql4_Design_Theme
221
     * app/code/core/Mage/Payment/Model/Paygate/Request.php             -> Mage_Paygate_Model_Authorizenet_Request
222
     * app/code/core/Mage/Dataflow/Model/Convert/Iterator.php           -> Mage_Dataflow_Model_Session_Adapter_Iterator
223
     *
224
     * @param SplFileInfo     $file
225
     * @param string          $className
226
     * @param OutputInterface $output
227
     * @return bool
228
     */
229
    protected function isClassDefinedInFile(SplFileInfo $file, $className, OutputInterface $output)
230
    {
231
        try {
232
            return preg_match("/class\s+{$className}/m", $file->getContents());
233
        } catch (Exception $e) {
234
            $output->writeln('<error>File: ' . $file->__toString() . ' | ' . $e->getMessage() . '</error>');
235
            return false;
236
        }
237
    }
238
239
    /**
240
     * Resource helper is always one per module for each db type and uses model alias
241
     *
242
     * @return array
243
     */
244
    protected function getResourceHelperMap()
245
    {
246
        $classes = array();
247
248
        if (($this->_magentoEnterprise && version_compare(\Mage::getVersion(), '1.11.2.0', '<='))
249
            || (!$this->_magentoEnterprise && version_compare(\Mage::getVersion(), '1.6.2.0', '<'))
250
        ) {
251
            return $classes;
252
        }
253
254
        $modelAliases = array_keys((array) \Mage::getConfig()->getNode('global/models'));
255
        foreach ($modelAliases as $modelAlias) {
256
            $resourceHelper = @\Mage::getResourceHelper($modelAlias);
257
            if (is_object($resourceHelper)) {
258
                $classes[$modelAlias] = get_class($resourceHelper);
259
            }
260
        }
261
262
        return $classes;
263
    }
264
265
    /**
266
     * @param string $group
267
     * @param OutputInterface $output
268
     *
269
     *@return array
270
     */
271
    protected function getClassMapForGroup($group, OutputInterface $output)
272
    {
273
        /**
274
         * Generate resource helper only for Magento >= EE 1.11 or CE 1.6
275
         */
276
        if ($group == 'resource helpers') {
277
            return $this->getResourceHelperMap();
278
        }
279
280
        $classes = array();
281
        foreach ($this->getGroupXmlDefinition($group) as $prefix => $modelDefinition) {
282
            if ($group == 'resource models') {
283
                if (empty($modelDefinition->resourceModel)) {
284
                    continue;
285
                }
286
                $resourceModelNodePath = 'global/models/' . strval($modelDefinition->resourceModel);
287
                $resourceModelConfig = \Mage::getConfig()->getNode($resourceModelNodePath);
288
                if ($resourceModelConfig) {
289
                    $classPrefix = strval($resourceModelConfig->class);
290
                }
291
            } else {
292
                $classPrefix = strval($modelDefinition->class);
293
            }
294
295
            if (empty($classPrefix)) {
296
                continue;
297
            }
298
299
            $classBaseFolder = str_replace('_', '/', $classPrefix);
300
            $searchFolders = array(
301
                \Mage::getBaseDir('code') . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . $classBaseFolder,
302
                \Mage::getBaseDir('code') . DIRECTORY_SEPARATOR . 'community' . DIRECTORY_SEPARATOR . $classBaseFolder,
303
                \Mage::getBaseDir('code') . DIRECTORY_SEPARATOR . 'local' . DIRECTORY_SEPARATOR . $classBaseFolder,
304
            );
305
            foreach ($searchFolders as $key => $folder) {
306
                if (!is_dir($folder)) {
307
                    unset($searchFolders[$key]);
308
                }
309
            }
310
311
            if (empty($searchFolders)) {
312
                continue;
313
            }
314
315
            $finder = Finder::create();
316
            $finder
317
                ->files()
318
                ->in($searchFolders)
319
                ->followLinks()
320
                ->ignoreUnreadableDirs(true)
321
                ->name('*.php')
322
                ->notName('install-*')
323
                ->notName('upgrade-*')
324
                ->notName('mysql4-*')
325
                ->notName('mssql-*')
326
                ->notName('oracle-*');
327
328
            foreach ($finder as $file) {
329
                $classIdentifier = $this->getClassIdentifier($file, $prefix, $group);
330
                $classNameByPath = $this->getRealClassname($file, $classPrefix);
331
332
                switch ($group) {
333
                    case 'blocks':
334
                        $classNameAfterRewrites = \Mage::getConfig()->getBlockClassName($classIdentifier);
335
                        break;
336
337
                    case 'helpers':
338
                        $classNameAfterRewrites = \Mage::getConfig()->getHelperClassName($classIdentifier);
339
                        break;
340
341
                    case 'models':
342
                        $classNameAfterRewrites = \Mage::getConfig()->getModelClassName($classIdentifier);
343
                        break;
344
345
                    case 'resource models':
346
                    default:
347
                        $classNameAfterRewrites = \Mage::getConfig()->getResourceModelClassName($classIdentifier);
348
                        break;
349
                }
350
351
                if ($classNameAfterRewrites) {
352
                    $addToList = true;
353
                    if ($classNameAfterRewrites === $classNameByPath
354
                        && !$this->isClassDefinedInFile($file, $classNameByPath, $output)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->isClassDefinedInF...assNameByPath, $output) of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
355
                    ) {
356
                        $addToList = false;
357
                    }
358
359
                    if ($addToList) {
360
                        $classes[$classIdentifier] = $classNameAfterRewrites;
361
362
                        if ($group == 'helpers' && strpos($classIdentifier, '/') === false) {
363
                            $classes[$classIdentifier . '/data'] = $classNameAfterRewrites;
364
                        }
365
                    }
366
                }
367
            }
368
        }
369
370
        return $classes;
371
    }
372
373
    /**
374
     * @param InputInterface $input
375
     * @param OutputInterface $output
376
     * @param $classMaps
377
     */
378
    protected function writeToOutputOld(InputInterface $input, OutputInterface $output, $classMaps)
379
    {
380
        $map = <<<PHP
381
<?php
382
namespace PHPSTORM_META {
383
    /** @noinspection PhpUnusedLocalVariableInspection */
384
    /** @noinspection PhpIllegalArrayKeyTypeInspection */
385
    /** @noinspection PhpLanguageLevelInspection */
386
    \$STATIC_METHOD_TYPES = [
387
PHP;
388
        $map .= "\n";
389 View Code Duplication
        foreach ($this->groupFactories as $group => $methods) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
390
            foreach ($methods as $method) {
391
                $map .= "        " . $method . "('') => [\n";
392
                foreach ($classMaps[$group] as $classPrefix => $class) {
393
                    if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {
394
                        $map .= "            '$classPrefix' instanceof \\$class,\n";
395
                    } else {
396
                        $output->writeln('<warning>Invalid class name <comment>' . $class . '</comment> ignored</warning>');
397
                    }
398
                }
399
                $map .= "        ], \n";
400
            }
401
        }
402
        $map .= <<<PHP
403
    ];
404
}
405
PHP;
406
        if ($input->getOption('stdout')) {
407
            $output->writeln($map);
408
        } else {
409
            if (\file_put_contents($this->_magentoRootFolder . '/.phpstorm.meta.php', $map)) {
410
                $output->writeln('<info>File <comment>.phpstorm.meta.php</comment> generated</info>');
411
            }
412
        }
413
    }
414
415
    /**
416
     * @param InputInterface $input
417
     * @param OutputInterface $output
418
     * @param $classMaps
419
     */
420
    protected function writeToOutputV2017(InputInterface $input, OutputInterface $output, $classMaps)
421
    {
422
        $baseMap = <<<PHP
423
<?php
424
namespace PHPSTORM_META {
425
    /** @noinspection PhpUnusedLocalVariableInspection */
426
    /** @noinspection PhpIllegalArrayKeyTypeInspection */
427
    /** @noinspection PhpLanguageLevelInspection */
428
    \$STATIC_METHOD_TYPES = [
429
PHP;
430
        $baseMap .= "\n";
431
        foreach ($this->groupFactories as $group => $methods) {
432
            $map = $baseMap;
433 View Code Duplication
            foreach ($methods as $method) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
434
                $map .= "        " . $method . "('') => [\n";
435
                asort($classMaps[$group]);
436
                foreach ($classMaps[$group] as $classPrefix => $class) {
437
                    if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {
438
                        $map .= "            '$classPrefix' instanceof \\$class,\n";
439
                    } else {
440
                        $output->writeln('<warning>Invalid class name <comment>' . $class . '</comment> ignored</warning>');
441
                    }
442
                }
443
                $map .= "        ], \n";
444
            }
445
            $map .= <<<PHP
446
    ];
447
}
448
PHP;
449
            if ($input->getOption('stdout')) {
450
                $output->writeln($map);
451
            } else {
452
                $metaPath = $this->_magentoRootFolder . '/.phpstorm.meta.php';
453
                if (is_file($metaPath)) {
454
                    if (\unlink($metaPath)) {
455
                        $output->writeln('<info>Deprecated file <comment>.phpstorm.meta.php</comment> removed</info>');
456
                    }
457
                }
458
                if (!is_dir($metaPath)) {
459
                    if (\mkdir($metaPath)) {
460
                        $output->writeln('<info>Directory <comment>.phpstorm.meta.php</comment> created</info>');
461
                    }
462
                }
463
                $group = str_replace(array(' ', '/'), '_', $group);
464
                if (\file_put_contents($this->_magentoRootFolder . '/.phpstorm.meta.php/magento_' . $group . '.meta.php', $map)) {
465
                    $output->writeln('<info>File <comment>.phpstorm.meta.php/magento_' . $group . '.meta.php</comment> generated</info>');
466
                }
467
            }
468
        }
469
470
        $baseMap = <<<PHP
471
<?php
472
namespace PHPSTORM_META {
473
PHP;
474
        $baseMap .= "\n";
475
        foreach ($this->methodFactories as $group => $methods) {
476
            $map = $baseMap;
477
            foreach ($methods as $method) {
478
                $map .= "    override( " . $method . "(0),\n";
479
                $map .= "        map( [\n";
480
                asort($classMaps[$group]);
481
                foreach ($classMaps[$group] as $classPrefix => $class) {
482
                    if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {
483
                        $map .= "            '$classPrefix' => \\$class::class,\n";
484
                    } else {
485
                        $output->writeln('<warning>Invalid class name <comment>' . $class . '</comment> ignored</warning>');
486
                    }
487
                }
488
                $map .= "        ])\n";
489
                $map .= "    );\n";
490
            }
491
            $map .= <<<PHP
492
}
493
PHP;
494
            if ($input->getOption('stdout')) {
495
                $output->writeln($map);
496
            } else {
497
                $group = str_replace(array(' ', '/'), '_', $group);
498
                if (\file_put_contents($this->_magentoRootFolder . '/.phpstorm.meta.php/magento_' . $group . '_methods.meta.php', $map)) {
499
                    $output->writeln('<info>File <comment>.phpstorm.meta.php/magento_' . $group . '_methods.meta.php</comment> generated</info>');
500
                }
501
            }
502
        }
503
    }
504
505
    /**
506
     * @param string $group
507
     * @return \Mage_Core_Model_Config_Element
508
     */
509
    protected function getGroupXmlDefinition($group)
510
    {
511
        if ($group == 'resource models') {
512
            $group = 'models';
513
        }
514
515
        $definitions = \Mage::getConfig()->getNode('global/' . $group);
516
517
        switch ($group) {
518
            case 'blocks':
519
                $groupClassType = 'Block';
520
                break;
521
522
            case 'helpers':
523
                $groupClassType = 'Helper';
524
                break;
525
526
            case 'models':
527
                $groupClassType = 'Model';
528
                break;
529
530
            default:
531
                return $definitions->children();
532
        }
533
534
        foreach ($this->missingHelperDefinitionModules as $moduleName) {
535
            $children = new Varien_Simplexml_Element(sprintf("<%s/>", strtolower($moduleName)));
536
            $children->class = sprintf('Mage_%s_%s', $moduleName, $groupClassType);
537
            $definitions->appendChild($children);
538
        }
539
540
        return $definitions->children();
541
    }
542
}
543