AbstractPluginGenerator   C
last analyzed

Complexity

Total Complexity 73

Size/Duplication

Total Lines 480
Duplicated Lines 8.96 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 43
loc 480
rs 5.5447
c 0
b 0
f 0
wmc 73
lcom 1
cbo 7

17 Methods

Rating   Name   Duplication   Size   Complexity  
getHeader() 0 1 ?
start() 0 1 ?
initFieldSet() 0 1 ?
A __construct() 0 5 1
A init() 0 7 1
C run() 0 43 8
A exitGenerator() 0 4 1
D makeLineRequest() 0 98 28
A getNestingLevel() 0 4 1
A setNestingLevel() 0 4 1
A getPluginCodes() 0 15 2
B generateEntities() 10 37 4
A generateMigration() 5 16 3
B makeMigration() 0 39 6
B makeCreateParts() 0 40 6
A makeDropParts() 0 9 2
D completeMessage() 28 38 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * This program is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU General Public License
12
 * as published by the Free Software Foundation; either version 2
13
 * of the License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
 */
24
25
namespace Eccube\Command\GeneratorCommand;
26
27
use Doctrine\Common\Inflector\Inflector;
28
use Doctrine\ORM\Mapping\ClassMetadataInfo;
29
use Doctrine\ORM\Tools\EntityGenerator;
30
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
31
use Eccube\Application;
32
use Symfony\Component\Console\Question\Question;
33
use Symfony\Component\Finder\Finder;
34
35
abstract class AbstractPluginGenerator
0 ignored issues
show
introduced by
Missing class doc comment
Loading history...
36
{
37
38
    const DEFAULT_NESTING_LEVEL = 100;
39
    const NEW_HOOK_VERSION = '3.0.9';
40
    const STOP_PROCESS = 'quit';
41
    const INPUT_OPEN = '[';
42
    const INPUT_CLOSE = ']';
43
    const PLUGIN_PREFIX = 'plg_';
44
45
    /**
46
     * app
47
     *
48
     * @var \Eccube\Application
49
     */
50
    protected $app;
51
52
    /**
53
     * QuestionHelper
54
     *
55
     * @var \Symfony\Component\Console\Helper\QuestionHelper
56
     */
57
    protected $dialog;
58
59
    /**
60
     * InputInterface
61
     *
62
     * @var \Symfony\Component\Console\Input\InputInterface
63
     */
64
    protected $input;
65
66
    /**
67
     * InputInterface
68
     *
69
     * @var \Symfony\Component\Console\Output\OutputInterface
70
     */
71
    protected $output;
72
73
    /**
74
     * $paramList
75
     * @var array $paramList
76
     */
77
    protected $paramList;
78
79
    /**
80
     *
81
     * @var int
82
     */
83
    private $nestingLevel;
84
85
    /**
86
     * ヘッダー
87
     */
88
    abstract protected function getHeader();
89
90
    /**
91
     * start()
92
     */
93
    abstract protected function start();
94
95
    /**
96
     * フィルドーセット
97
     */
98
    abstract protected function initFieldSet();
99
100
    public function __construct(Application $app)
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
101
    {
102
        $this->app = $app;
103
        $this->nestingLevel = self::DEFAULT_NESTING_LEVEL;
104
    }
105
106
    /**
107
     *
108
     * @param \Symfony\Component\Console\Helper\QuestionHelper $dialog
0 ignored issues
show
introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
109
     * @param \Symfony\Component\Console\Input\InputInterface $input
0 ignored issues
show
introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
110
     * @param \Symfony\Component\Console\Output\OutputInterface $output
111
     */
112
    public function init($dialog, $input, $output)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
113
    {
114
        $this->dialog = $dialog;
115
        $this->input = $input;
116
        $this->output = $output;
117
        $this->initFieldSet();
118
    }
119
120
    public function run()
0 ignored issues
show
introduced by
Missing function doc comment
Loading history...
121
    {
122
        // ヘッダー部分
123
        $this->getHeader();
124
125
        foreach ($this->paramList as $paramKey => $params) {
126
            $value = $this->makeLineRequest($params);
127
            if ($value === false) {
128
                $this->exitGenerator();
129
130
                return;
131
            }
132
            $this->paramList[$paramKey]['value'] = $value;
133
        }
134
135
        $this->output->writeln('');
136
        $this->output->writeln('---Entry confirmation');
137
        foreach ($this->paramList as $paramKey => $params) {
138
            if (is_array($params['value'])) {
139
                $this->output->writeln($params['label']);
140
                foreach ($params['value'] as $keys => $val) {
141
                    $this->output->writeln('<info>  '.$keys.'</info>');
142
                }
143
            } else {
144
                if (isset($params['show'])) {
145
                    $disp = $params['show'][$params['value']];
146
                } else {
147
                    $disp = $params['value'];
148
                }
149
                $this->output->writeln($params['label'].' <info>'.$disp.'</info>');
150
            }
151
        }
152
        $this->output->writeln('');
153
        $Question = new Question('<comment>[confirm] Do you want to proceed? [y/n] : </comment>', '');
154
        $value = $this->dialog->ask($this->input, $this->output, $Question);
155
        if ($value != 'y') {
156
            $this->exitGenerator();
157
158
            return;
159
        }
160
161
        $this->start();
162
    }
163
164
    protected function exitGenerator($msg = 'Quitting Bye bye.')
165
    {
166
        $this->output->writeln($msg);
167
    }
168
169
    protected function makeLineRequest($params)
170
    {
171
        // nesting loop protection
172
        if ($this->getNestingLevel() < 0) {
173
            rewind($this->output->getStream());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method getStream() does only exist in the following implementations of said interface: Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\StreamOutput.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
174
            $display = stream_get_contents($this->output->getStream());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method getStream() does only exist in the following implementations of said interface: Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\StreamOutput.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
175
            throw new \Exception($display);
176
        }
177
        $this->nestingLevel--;
178
179
        $this->output->writeln($params['name']);
180
        $Question = new Question('<comment>Input'.self::INPUT_OPEN.$params['no'].self::INPUT_CLOSE.' : </comment>', '');
181
        $value = $this->dialog->ask($this->input, $this->output, $Question);
182
        $value = trim($value);
183
        if ($value === self::STOP_PROCESS) {
184
            return false;
185
        }
186
        foreach ($params['validation'] as $key => $row) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
187
188
            if ($key == 'isRequired' && $row == true) {
189
                if ($value === '' || strlen($value) == 0) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
190
191
                    $this->output->writeln('[!] Value cannot be empty.');
192
193
                    return $this->makeLineRequest($params);
194
                }
195
            } elseif ($key == 'pattern' && preg_match($row, $value) == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match($row, $value) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
196
                $this->output->writeln('<error>[!] Value is not valid.</error>');
197
198
                return $this->makeLineRequest($params);
199
            } elseif ($key == 'isCode') {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
200
201
                if (in_array($value, $row)) {
202
                    $this->output->writeln('<error>[!] Plugin with this code already exists.</error>');
203
204
                    return $this->makeLineRequest($params);
205
                }
206
            } elseif ($key == 'isNotCode') {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
207
208
                if (!in_array($value, $row)) {
209
                    $this->output->writeln('<error>[!] This plugin code does not exist.</error>');
210
211
                    return $this->makeLineRequest($params);
212
                }
213
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
214
            } elseif ($key == 'inArray' || $key == 'choice') {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
215
216
                if (is_string($row)) {
217
                    $row = $this->$row();
218
                }
219
                if ($value == '') {
220
                    return $params['value'];
221
                }
222
                if (isset($row[$value])) {
223
                    if (!is_array($params['value'])) {
224
                        $value = $row[$value];
225
                        continue;
226
                    }
227
                    $params['value'][$value] = $row[$value];
228
                    $this->output->writeln('<info>--- your entry list</info>');
229
                    foreach ($params['value'] as $subKey => $node) {
230
                        $this->output->writeln('<info> - '.$subKey.'</info>');
231
                    }
232
                    $this->output->writeln('');
233
                    $this->output->writeln('--- Press Enter to move to the next step ---');
234
235
                    return $this->makeLineRequest($params);
236
                } else {
237
                    $searchList = array();
238
                    $max = 16;
239
                    foreach ($row as $eventKey => $eventConst) {
240
                        if (strpos($eventKey, $value) !== false || strpos($eventConst, $value) !== false) {
241
                            if (count($searchList) >= $max) {
242
                                $searchList['-- there are more then '.$max.''] = '';
243
                                break;
244
                            }
245
                            $searchList[$eventKey] = $eventConst;
246
                        }
247
                    }
248
                    $this->output->writeln('<error>[!] No results have been found</error>');
249
                    if (!empty($searchList)) {
250
                        $this->output->writeln('--- there are more then one search result');
251
                    }
252
                    foreach ($searchList as $subKey => $node) {
253
                        $this->output->writeln(' - '.$subKey);
254
                    }
255
256
                    if (!empty($searchList)) {
257
                        $this->output->writeln('');
258
                    }
259
260
                    return $this->makeLineRequest($params);
261
                }
262
            }
263
        }
264
265
        return $value;
266
    }
267
268
    protected function getNestingLevel()
269
    {
270
        return $this->nestingLevel;
271
    }
272
273
    protected function setNestingLevel($nestingLevel)
274
    {
275
        $this->nestingLevel = $nestingLevel;
276
    }
277
278
279
    /**
280
     * app/Plugin直下にあるディレクトリ名(プラグインコード)を取得
281
     *
282
     * @return array
283
     */
284
    protected function getPluginCodes()
285
    {
286
        $finder = new Finder();
287
288
        $finder->directories()->depth('== 0');
289
290
        $dirs = $finder->in($this->app['config']['root_dir'].'/app/Plugin/');
291
292
        $codes = array();
293
        foreach ($dirs as $item) {
294
            $codes[] = $item->getRelativePathname();
295
        }
296
297
        return $codes;
298
    }
299
300
    /**
301
     * Entity、Repositoryファイルの作成
302
     *
303
     * @param array $metadatas
304
     * @param array $fsList
305
     */
306
    protected function generateEntities(array $metadatas, array &$fsList)
307
    {
308
        /** @var ClassMetadataInfo $class */
309
        foreach ($metadatas as $class) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
310
311
            // Entity作成
312
            $EntityGenerator = new EntityGenerator();
313
            $EntityGenerator->setBackupExisting(false);
314
            $EntityGenerator->setClassToExtend('Eccube\\Entity\\AbstractEntity');
315
            $EntityGenerator->setGenerateAnnotations(false);
316
            $EntityGenerator->setRegenerateEntityIfExists(true);
317
            $EntityGenerator->setGenerateStubMethods(true);
318
            $EntityGenerator->setUpdateEntityIfExists(false);
319
320
            $appPath = $this->app['config']['root_dir'].'/app/';
321
            $EntityGenerator->generate(array($class), $appPath);
322
323
            $filename = $appPath.str_replace('\\', DIRECTORY_SEPARATOR, $class->name).'.php';
324 View Code Duplication
            if (is_file($filename)) {
325
                $fsList['file'][$filename] = true;
326
            } else {
327
                $fsList['file'][$filename] = false;
328
            }
329
330
            // Repository作成
331
            $RepositoryGenerator = new EntityRepositoryGenerator();
332
            $RepositoryGenerator->writeEntityRepositoryClass($class->customRepositoryClassName, $appPath);
333
334
            $filename = $appPath.str_replace('\\', DIRECTORY_SEPARATOR, $class->customRepositoryClassName).'.php';
335 View Code Duplication
            if (is_file($filename)) {
336
                $fsList['file'][$filename] = true;
337
            } else {
338
                $fsList['file'][$filename] = false;
339
            }
340
        }
341
342
    }
343
344
345
    /**
346
     * migraionファイルの作成
347
     *
348
     * @param array $metadatas
349
     * @param array $fsList
350
     * @param $pluginCode
351
     * @param $codePath
352
     */
353
    protected function generateMigration(array $metadatas, array &$fsList = array(), $pluginCode, $codePath)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
354
    {
355
        if (count($metadatas)) {
356
            $migrationContent = $this->makeMigration($pluginCode, $metadatas);
357
            $date = date('YmdHis');
358
            $migrationContent = str_replace('[datetime]', $date, $migrationContent);
359
            $migPath = $codePath.'/Resource/doctrine/migration/Version'.$date.'.php';
360
361
            file_put_contents($migPath, $migrationContent);
362 View Code Duplication
            if (is_file($migPath)) {
363
                $fsList['file'][$migPath] = true;
364
            } else {
365
                $fsList['file'][$migPath] = false;
366
            }
367
        }
368
    }
369
370
371
    /**
372
     * migrationファイルの作成
373
     *
374
     * @param $pluginCode
375
     * @param array $metadatas
376
     * @return mixed|string
377
     */
378
    protected function makeMigration($pluginCode, array $metadatas)
379
    {
380
        if ($this->paramList['supportFlag']['value']) {
381
            $migrationFileCont = file_get_contents($this->app['config']['root_dir'].'/src/Eccube/Command/GeneratorCommand/generatortemplate/MigrationVersionSupport.php');
382
        } else {
383
            $migrationFileCont = file_get_contents($this->app['config']['root_dir'].'/src/Eccube/Command/GeneratorCommand/generatortemplate/MigrationVersion.php');
384
        }
385
386
        $migrationFileCont = str_replace('[code]', $pluginCode, $migrationFileCont);
387
388
        $entityList = array();
389
        foreach ($metadatas as $metadata) {
390
            $entityList[] = '        \''.$metadata->name.'\'';
391
        }
392
393
        $entityListStr = join(','.PHP_EOL, $entityList);
394
        $migrationFileCont = str_replace('[entityList]', $entityListStr, $migrationFileCont);
395
        if ($this->paramList['supportFlag']['value']) {
396
            $createParts = $this->makeCreateParts($metadatas);
397
            $tableNameArr = array();
398
            foreach ($createParts as $tableName => $tableArr) {
399
                $tableNameArr[] = '            $this->createTable'.$tableName.'($schema);';
400
            }
401
            $tableNameStr = join(PHP_EOL, $tableNameArr);
402
            $migrationFileCont = str_replace('[createTable]', $tableNameStr, $migrationFileCont);
403
404
            $createPartsStr = '';
405
            foreach ($createParts as $parts) {
406
                $createPartsStr .= join(PHP_EOL, $parts);
407
            }
408
            $migrationFileCont = str_replace('[createFunction]', $createPartsStr, $migrationFileCont);
409
410
            $dropParts = $this->makeDropParts($metadatas);
411
            $dropPartsStr = join(PHP_EOL, $dropParts);
412
            $migrationFileCont = str_replace('[dropTable]', $dropPartsStr, $migrationFileCont);
413
        }
414
415
        return $migrationFileCont;
416
    }
417
418
419
    protected function makeCreateParts($metadatas)
420
    {
421
        $ret = array();
422
        foreach ($metadatas as $metadata) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
423
424
            $nameFormated = Inflector::camelize($metadata->table['name']);
425
            $tmp = array();
426
            $tmp[] = '';
427
            $tmp[] = '    /**';
428
            $tmp[] = '     * @param Schema $schema';
429
            $tmp[] = '     */';
430
            $tmp[] = '    public function createTable'.ucfirst($nameFormated).'(Schema $schema)';
431
            $tmp[] = '    {';
432
            $tmp[] = '        $table = $schema->createTable(\''.$metadata->table['name'].'\');';
433
            $columns = $metadata->fieldMappings;
434
            foreach ($columns as $column) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
435
436
                $typeName = $column['type'];
437
                $tmp[] = '        $table->addColumn(\''.$column['columnName'].'\', \''.$typeName.'\', array(';
438
                $param = array();
439
                if (isset($column['nullable']) && $column['nullable']) {
440
                    $param['notnull'] = 'true';
441
                } else {
442
                    $param['notnull'] = 'false';
443
                }
444
445
                foreach ($param as $parKey => $parVal) {
446
                    $tmp[] = '            \''.$parKey.'\' => '.$parVal.',';
447
                }
448
                $tmp[] = '        ));';
449
            }
450
451
452
            $tmp[] = '    }';
453
            $tmp[] = '';
454
            $ret[ucfirst($nameFormated)] = $tmp;
455
        }
456
457
        return $ret;
458
    }
459
460
    protected function makeDropParts($metadatas)
461
    {
462
        $ret = array();
463
        foreach ($metadatas as $metadata) {
464
            $ret[] = '            $schema->dropTable(\''.$metadata->table['name'].'\');';
465
        }
466
467
        return $ret;
468
    }
469
470
471
    /**
472
     * メッセージ表示
473
     *
474
     * @param array $fsList
475
     */
476
    protected function completeMessage(array $fsList)
477
    {
478
479
        $dirFileNg = array();
480
        $dirFileOk = array();
481 View Code Duplication
        foreach ($fsList['dir'] as $path => $flag) {
482
            if ($flag) {
483
                $dirFileOk[] = $path;
484
            } else {
485
                $dirFileNg[] = $path;
486
            }
487
        }
488 View Code Duplication
        foreach ($fsList['file'] as $path => $flag) {
489
            if ($flag) {
490
                $dirFileOk[] = $path;
491
            } else {
492
                $dirFileNg[] = $path;
493
            }
494
        }
495
        $this->output->writeln('');
496
        $this->output->writeln('[+]File system');
497 View Code Duplication
        if (!empty($dirFileOk)) {
498
            $this->output->writeln('');
499
            $this->output->writeln(' this files and folders were created.');
500
            foreach ($dirFileOk as $path) {
501
                $this->output->writeln('<info> - '.$path.'</info>');
502
            }
503
        }
504
505 View Code Duplication
        if (!empty($dirFileNg)) {
506
            $this->output->writeln('');
507
            $this->output->writeln(' this files and folders was not created.');
508
            foreach ($dirFileOk as $path) {
509
                $this->output->writeln('<error> - '.$path.'</error>');
510
            }
511
        }
512
513
    }
514
}
515