Completed
Push — develop ( 21139f...eaa140 )
by Tom
12s
created

MetaCommand   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 393
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 54
c 3
b 1
f 0
lcom 1
cbo 3
dl 0
loc 393
rs 7.0642

9 Methods

Rating   Name   Duplication   Size   Complexity  
B execute() 0 25 6
A configure() 0 8 1
A getRealClassname() 0 13 2
A getClassIdentifier() 0 12 3
A isClassDefinedInFile() 0 9 2
B getResourceHelperMap() 0 20 7
F getClassMapForGroup() 0 101 21
B writeToOutput() 0 32 6
B getGroupXmlDefinition() 0 33 6

How to fix   Complexity   

Complex Class

Complex classes like MetaCommand 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 MetaCommand, and based on these observations, apply Extract Interface, too.

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
     * @var array
55
     */
56
    protected $missingHelperDefinitionModules = array(
57
        'Backup',
58
        'Bundle',
59
        'Captcha',
60
        'Catalog',
61
        'Centinel',
62
        'Checkout',
63
        'Cms',
64
        'Core',
65
        'Customer',
66
        'Dataflow',
67
        'Directory',
68
        'Downloadable',
69
        'Eav',
70
        'Index',
71
        'Install',
72
        'Log',
73
        'Media',
74
        'Newsletter',
75
        'Page',
76
        'Payment',
77
        'Paypal',
78
        'Persistent',
79
        'Poll',
80
        'Rating',
81
        'Reports',
82
        'Review',
83
        'Rss',
84
        'Rule',
85
        'Sales',
86
        'Shipping',
87
        'Sitemap',
88
        'Tag',
89
        'Tax',
90
        'Usa',
91
        'Weee',
92
        'Widget',
93
        'Wishlist',
94
    );
95
96
    protected function configure()
97
    {
98
        $this
99
            ->setName('dev:ide:phpstorm:meta')
100
            ->addOption('stdout', null, InputOption::VALUE_NONE, 'Print to stdout instead of file .phpstorm.meta.php')
101
            ->setDescription('Generates meta data file for PhpStorm auto completion')
102
        ;
103
    }
104
105
    /**
106
     * @param InputInterface  $input
107
     * @param OutputInterface $output
108
     *
109
     * @internal param string $package
110
     * @return void
111
     */
112
    protected function execute(InputInterface $input, OutputInterface $output)
113
    {
114
        $this->detectMagento($output);
115
        if (!$this->initMagento()) {
116
            return;
117
        }
118
119
        if ($this->_magentoMajorVersion == self::MAGENTO_MAJOR_VERSION_1) {
120
            $classMaps = array();
121
122
            foreach ($this->groups as $group) {
123
                $classMaps[$group] = $this->getClassMapForGroup($group, $output);
124
125
                if (!$input->getOption('stdout') && count($classMaps[$group]) > 0) {
126
                    $output->writeln(
127
                        '<info>Generated definitions for <comment>' . $group . '</comment> group</info>'
128
                    );
129
                }
130
            }
131
132
            $this->writeToOutput($input, $output, $classMaps);
133
        } else {
134
            $output->write('Magento 2 is currently not supported');
135
        }
136
    }
137
138
    /**
139
     * @param SplFileInfo $file
140
     * @param string $classPrefix
141
     * @return string
142
     */
143
    protected function getRealClassname(SplFileInfo $file, $classPrefix)
144
    {
145
        $path = $file->getRelativePathname();
146
        if (substr($path, -4) !== '.php') {
147
            throw new UnexpectedValueException(
148
                sprintf('Expected that relative file %s ends with ".php"', var_export($path, true))
149
            );
150
        }
151
        $path = substr($path, 0, -4);
152
        $path = strtr($path, '\\', '/');
153
154
        return trim($classPrefix . '_' . strtr($path, '/', '_'), '_');
155
    }
156
157
    /**
158
     * @param SplFileInfo   $file
159
     * @param string        $classPrefix
160
     * @param string        $group
161
     * @return string
162
     */
163
    protected function getClassIdentifier(SplFileInfo $file, $classPrefix, $group = '')
164
    {
165
        $path = str_replace('.php', '', $file->getRelativePathname());
166
        $path = str_replace('\\', '/', $path);
167
        $parts = explode('/', $path);
168
        $parts = array_map('lcfirst', $parts);
169
        if ($path == 'Data' && ($group == 'helpers')) {
170
            array_pop($parts);
171
        }
172
173
        return rtrim($classPrefix . '/' . implode('_', $parts), '/');
174
    }
175
176
    /**
177
     * Verify whether given class is defined in given file because there is no sense in adding class with incorrect
178
     * file or path. Examples:
179
     * app/code/core/Mage/Core/Model/Mysql4/Design/Theme/Collection.php -> Mage_Core_Model_Mysql4_Design_Theme
180
     * app/code/core/Mage/Payment/Model/Paygate/Request.php             -> Mage_Paygate_Model_Authorizenet_Request
181
     * app/code/core/Mage/Dataflow/Model/Convert/Iterator.php           -> Mage_Dataflow_Model_Session_Adapter_Iterator
182
     *
183
     * @param SplFileInfo     $file
184
     * @param string          $className
185
     * @param OutputInterface $output
186
     * @return bool
187
     */
188
    protected function isClassDefinedInFile(SplFileInfo $file, $className, OutputInterface $output)
189
    {
190
        try {
191
            return preg_match("/class\s+{$className}/m", $file->getContents());
192
        } catch (Exception $e) {
193
            $output->writeln('<error>File: ' . $file->__toString() . ' | ' . $e->getMessage() . '</error>');
194
            return false;
195
        }
196
    }
197
198
    /**
199
     * Resource helper is always one per module for each db type and uses model alias
200
     *
201
     * @return array
202
     */
203
    protected function getResourceHelperMap()
204
    {
205
        $classes = array();
206
207
        if (($this->_magentoEnterprise && version_compare(\Mage::getVersion(), '1.11.2.0', '<='))
208
            || (!$this->_magentoEnterprise && version_compare(\Mage::getVersion(), '1.6.2.0', '<'))
209
        ) {
210
            return $classes;
211
        }
212
213
        $modelAliases = array_keys((array) \Mage::getConfig()->getNode('global/models'));
214
        foreach ($modelAliases as $modelAlias) {
215
            $resourceHelper = @\Mage::getResourceHelper($modelAlias);
216
            if (is_object($resourceHelper)) {
217
                $classes[$modelAlias] = get_class($resourceHelper);
218
            }
219
        }
220
221
        return $classes;
222
    }
223
224
    /**
225
     * @param string $group
226
     * @param OutputInterface $output
227
     *
228
*@return array
229
     */
230
    protected function getClassMapForGroup($group, OutputInterface $output)
231
    {
232
        /**
233
         * Generate resource helper only for Magento >= EE 1.11 or CE 1.6
234
         */
235
        if ($group == 'resource helpers') {
236
            return $this->getResourceHelperMap();
237
        }
238
239
        $classes = array();
240
        foreach ($this->getGroupXmlDefinition($group) as $prefix => $modelDefinition) {
241
            if ($group == 'resource models') {
242
                if (empty($modelDefinition->resourceModel)) {
243
                    continue;
244
                }
245
                $resourceModelNodePath = 'global/models/' . strval($modelDefinition->resourceModel);
246
                $resourceModelConfig = \Mage::getConfig()->getNode($resourceModelNodePath);
247
                if ($resourceModelConfig) {
248
                    $classPrefix = strval($resourceModelConfig->class);
249
                }
250
            } else {
251
                $classPrefix = strval($modelDefinition->class);
252
            }
253
254
            if (empty($classPrefix)) {
255
                continue;
256
            }
257
258
            $classBaseFolder = str_replace('_', '/', $classPrefix);
259
            $searchFolders = array(
260
                \Mage::getBaseDir('code') . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . $classBaseFolder,
261
                \Mage::getBaseDir('code') . DIRECTORY_SEPARATOR . 'community' . DIRECTORY_SEPARATOR . $classBaseFolder,
262
                \Mage::getBaseDir('code') . DIRECTORY_SEPARATOR . 'local' . DIRECTORY_SEPARATOR . $classBaseFolder,
263
            );
264
            foreach ($searchFolders as $key => $folder) {
265
                if (!is_dir($folder)) {
266
                    unset($searchFolders[$key]);
267
                }
268
            }
269
270
            if (empty($searchFolders)) {
271
                continue;
272
            }
273
274
            $finder = Finder::create();
275
            $finder
276
                ->files()
277
                ->in($searchFolders)
278
                ->followLinks()
279
                ->ignoreUnreadableDirs(true)
280
                ->name('*.php')
281
                ->notName('install-*')
282
                ->notName('upgrade-*')
283
                ->notName('mysql4-*')
284
                ->notName('mssql-*')
285
                ->notName('oracle-*');
286
287
            foreach ($finder as $file) {
288
                $classIdentifier = $this->getClassIdentifier($file, $prefix, $group);
289
                $classNameByPath = $this->getRealClassname($file, $classPrefix);
290
291
                switch ($group) {
292
                    case 'blocks':
293
                        $classNameAfterRewrites = \Mage::getConfig()->getBlockClassName($classIdentifier);
294
                        break;
295
296
                    case 'helpers':
297
                        $classNameAfterRewrites = \Mage::getConfig()->getHelperClassName($classIdentifier);
298
                        break;
299
300
                    case 'models':
301
                        $classNameAfterRewrites = \Mage::getConfig()->getModelClassName($classIdentifier);
302
                        break;
303
304
                    case 'resource models':
305
                    default:
306
                        $classNameAfterRewrites = \Mage::getConfig()->getResourceModelClassName($classIdentifier);
307
                        break;
308
                }
309
310
                if ($classNameAfterRewrites) {
311
                    $addToList = true;
312
                    if ($classNameAfterRewrites === $classNameByPath
313
                        && !$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...
314
                    ) {
315
                        $addToList = false;
316
                    }
317
318
                    if ($addToList) {
319
                        $classes[$classIdentifier] = $classNameAfterRewrites;
320
321
                        if ($group == 'helpers' && strpos($classIdentifier, '/') === false) {
322
                            $classes[$classIdentifier . '/data'] = $classNameAfterRewrites;
323
                        }
324
                    }
325
                }
326
            }
327
        }
328
329
        return $classes;
330
    }
331
332
    /**
333
     * @param InputInterface $input
334
     * @param OutputInterface $output
335
     * @param $classMaps
336
     */
337
    protected function writeToOutput(InputInterface $input, OutputInterface $output, $classMaps)
338
    {
339
        $map = <<<PHP
340
<?php
341
namespace PHPSTORM_META {
342
    /** @noinspection PhpUnusedLocalVariableInspection */
343
    /** @noinspection PhpIllegalArrayKeyTypeInspection */
344
    /** @noinspection PhpLanguageLevelInspection */
345
    \$STATIC_METHOD_TYPES = [
346
PHP;
347
        $map .= "\n";
348
        foreach ($this->groupFactories as $group => $methods) {
349
            foreach ($methods as $method) {
350
                $map .= "        " . $method . "('') => [\n";
351
                foreach ($classMaps[$group] as $classPrefix => $class) {
352
                    $map .= "            '$classPrefix' instanceof \\$class,\n";
353
                }
354
                $map .= "        ], \n";
355
            }
356
        }
357
        $map .= <<<PHP
358
    ];
359
}
360
PHP;
361
        if ($input->getOption('stdout')) {
362
            $output->writeln($map);
363
        } else {
364
            if (\file_put_contents($this->_magentoRootFolder . '/.phpstorm.meta.php', $map)) {
365
                $output->writeln('<info>File <comment>.phpstorm.meta.php</comment> generated</info>');
366
            }
367
        }
368
    }
369
370
    /**
371
     * @param string $group
372
     * @return \Mage_Core_Model_Config_Element
373
     */
374
    protected function getGroupXmlDefinition($group)
375
    {
376
        if ($group == 'resource models') {
377
            $group = 'models';
378
        }
379
380
        $definitions = \Mage::getConfig()->getNode('global/' . $group);
381
382
        switch ($group) {
383
            case 'blocks':
384
                $groupClassType = 'Block';
385
                break;
386
387
            case 'helpers':
388
                $groupClassType = 'Helper';
389
                break;
390
391
            case 'models':
392
                $groupClassType = 'Model';
393
                break;
394
395
            default:
396
                return $definitions->children();
397
        }
398
399
        foreach ($this->missingHelperDefinitionModules as $moduleName) {
400
            $children = new Varien_Simplexml_Element(sprintf("<%s/>", strtolower($moduleName)));
401
            $children->class = sprintf('Mage_%s_%s', $moduleName, $groupClassType);
402
            $definitions->appendChild($children);
403
        }
404
405
        return $definitions->children();
406
    }
407
}
408