Completed
Push — kurozumi-patch-9 ( 9ae5a9 )
by Kiyotaka
04:39
created

PluginGenerateCommand   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 583
Duplicated Lines 3.09 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 18
loc 583
rs 10
c 0
b 0
f 0
wmc 26
lcom 1
cbo 7

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A configure() 0 8 1
A initialize() 0 5 1
B interact() 18 35 7
A execute() 0 22 1
A validateCode() 0 19 5
A validateVersion() 0 5 1
A createDirectories() 0 18 2
A createConfig() 0 19 1
A createGithubActions() 0 35 1
A createMessages() 0 5 1
A createTwigBlock() 0 23 1
A createNav() 0 23 1
A createEvent() 0 23 1
B createConfigController() 0 278 1

How to fix   Duplicated Code   

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:

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Command;
15
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Exception\InvalidArgumentException;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\Console\Style\SymfonyStyle;
22
use Symfony\Component\DependencyInjection\Container;
23
use Symfony\Component\DependencyInjection\ContainerInterface;
24
use Symfony\Component\Filesystem\Filesystem;
25
26
class PluginGenerateCommand extends Command
27
{
28
    protected static $defaultName = 'eccube:plugin:generate';
29
30
    /**
31
     * @var SymfonyStyle
32
     */
33
    protected $io;
34
35
    /**
36
     * @var Filesystem
37
     */
38
    protected $fs;
39
40
    /**
41
     * @var ContainerInterface
42
     */
43
    protected $container;
44
45
    public function __construct(ContainerInterface $container)
46
    {
47
        parent::__construct();
48
        $this->container = $container;
49
    }
50
51
    protected function configure()
52
    {
53
        $this
54
            ->addArgument('name', InputOption::VALUE_REQUIRED, 'plugin name')
55
            ->addArgument('code', InputOption::VALUE_REQUIRED, 'plugin code')
56
            ->addArgument('ver', InputOption::VALUE_REQUIRED, 'plugin version')
57
            ->setDescription('Generate plugin skeleton.');
58
    }
59
60
    protected function initialize(InputInterface $input, OutputInterface $output)
61
    {
62
        $this->io = new SymfonyStyle($input, $output);
63
        $this->fs = new Filesystem();
64
    }
65
66
    protected function interact(InputInterface $input, OutputInterface $output)
67
    {
68
        if (null !== $input->getArgument('name') && null !== $input->getArgument('code') && null !== $input->getArgument('ver')) {
69
            return;
70
        }
71
72
        $this->io->title('EC-CUBE Plugin Generator Interactive Wizard');
73
74
        // Plugin name.
75
        $name = $input->getArgument('name');
76 View Code Duplication
        if (null !== $name) {
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...
77
            $this->io->text(' > <info>name</info>: '.$name);
78
        } else {
79
            $name = $this->io->ask('name', 'EC-CUBE Sample Plugin');
80
            $input->setArgument('name', $name);
81
        }
82
83
        // Plugin code.
84
        $code = $input->getArgument('code');
85 View Code Duplication
        if (null !== $code) {
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...
86
            $this->io->text(' > <info>code</info>: '.$code);
87
        } else {
88
            $code = $this->io->ask('code', 'Sample', [$this, 'validateCode']);
89
            $input->setArgument('code', $code);
90
        }
91
92
        // Plugin version.
93
        $version = $input->getArgument('ver');
94 View Code Duplication
        if (null !== $version) {
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...
95
            $this->io->text(' > <info>ver</info>: '.$version);
96
        } else {
97
            $version = $this->io->ask('ver', '1.0.0', [$this, 'validateVersion']);
98
            $input->setArgument('ver', $version);
99
        }
100
    }
101
102
    protected function execute(InputInterface $input, OutputInterface $output)
103
    {
104
        $name = $input->getArgument('name');
105
        $code = $input->getArgument('code');
106
        $version = $input->getArgument('ver');
107
108
        $this->validateCode($code);
109
        $this->validateVersion($version);
0 ignored issues
show
Unused Code introduced by
The call to the method Eccube\Command\PluginGen...mand::validateVersion() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
110
111
        $pluginDir = $this->container->getParameter('kernel.project_dir').'/app/Plugin/'.$code;
112
113
        $this->createDirectories($pluginDir);
114
        $this->createConfig($pluginDir, $name, $code, $version);
115
        $this->createEvent($pluginDir, $code);
116
        $this->createMessages($pluginDir);
117
        $this->createNav($pluginDir, $code);
118
        $this->createTwigBlock($pluginDir, $code);
119
        $this->createConfigController($pluginDir, $code);
120
        $this->createGithubActions($pluginDir);
121
122
        $this->io->success(sprintf('Plugin was successfully created: %s %s %s', $name, $code, $version));
123
    }
124
125
    public function validateCode($code)
126
    {
127
        if (empty($code)) {
128
            throw new InvalidArgumentException('The code can not be empty.');
129
        }
130
        if (strlen($code) > 255) {
131
            throw new InvalidArgumentException('The code can enter up to 255 characters');
132
        }
133
        if (1 !== preg_match('/^\w+$/', $code)) {
134
            throw new InvalidArgumentException('The code [a-zA-Z_] is available.');
135
        }
136
137
        $pluginDir = $this->container->getParameter('kernel.project_dir').'/app/Plugin/'.$code;
138
        if (file_exists($pluginDir)) {
139
            throw new InvalidArgumentException('Plugin directory exists.');
140
        }
141
142
        return $code;
143
    }
144
145
    public function validateVersion($version)
146
    {
147
        // TODO
148
        return $version;
149
    }
150
151
    /**
152
     * @param string $pluginDir
153
     */
154
    protected function createDirectories($pluginDir)
155
    {
156
        $dirs = [
157
            'Controller/Admin',
158
            'Entity',
159
            'Repository',
160
            'Form/Type',
161
            'Form/Extension',
162
            'Resource/doctrine',
163
            'Resource/locale',
164
            'Resource/template/admin',
165
            '.github/workflows',
166
        ];
167
168
        foreach ($dirs as $dir) {
169
            $this->fs->mkdir($pluginDir.'/'.$dir);
170
        }
171
    }
172
173
    /**
174
     * @param string $pluginDir
175
     */
176
    protected function createConfig($pluginDir, $name, $code, $version)
177
    {
178
        $source = <<<EOL
179
{
180
  "name": "ec-cube/$code",
181
  "version": "$version",
182
  "description": "$name",
183
  "type": "eccube-plugin",
184
  "require": {
185
    "ec-cube/plugin-installer": "~0.0.7"
186
  },
187
  "extra": {
188
    "code": "$code"
189
  }
190
}
191
EOL;
192
193
        $this->fs->dumpFile($pluginDir.'/composer.json', $source);
194
    }
195
196
197
    /**
198
     * @param string $pluginDir
199
     */
200
    protected function createGithubActions($pluginDir)
201
    {
202
        $source = '
203
name: Packaging for EC-CUBE Plugin
204
on:
205
  release:
206
    types: [ published ]
207
jobs:
208
  deploy:
209
    name: Build
210
    runs-on: ubuntu-18.04
211
    steps:
212
      - name: Checkout
213
        uses: actions/checkout@v2
214
      - name: Packaging
215
        working-directory: ../
216
        run: |
217
          rm -rf $GITHUB_WORKSPACE/.github
218
          find $GITHUB_WORKSPACE -name "dummy" -delete
219
          find $GITHUB_WORKSPACE -name ".git*" -and ! -name ".gitkeep" -print0 | xargs -0 rm -rf
220
          chmod -R o+w $GITHUB_WORKSPACE
221
          cd $GITHUB_WORKSPACE
222
          tar cvzf ../${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.tar.gz ./*
223
      - name: Upload binaries to release of TGZ
224
        uses: svenstaro/upload-release-action@v1-release
225
        with:
226
          repo_token: ${{ secrets.GITHUB_TOKEN }}
227
          file: ${{ runner.workspace }}/${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.tar.gz
228
          asset_name: ${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.tar.gz
229
          tag: ${{ github.ref }}
230
          overwrite: true
231
';
232
233
        $this->fs->dumpFile($pluginDir.'/.github/workflows/release.yml', $source);
234
    }
235
236
237
    /**
238
     * @param string $pluginDir
239
     */
240
    protected function createMessages($pluginDir)
241
    {
242
        $this->fs->dumpFile($pluginDir.'/Resource/locale/messages.ja.yaml', '');
243
        $this->fs->dumpFile($pluginDir.'/Resource/locale/validators.ja.yaml', '');
244
    }
245
246
    /**
247
     * @param string $pluginDir
248
     */
249
    protected function createTwigBlock($pluginDir, $code)
250
    {
251
        $source = <<<EOL
252
<?php
253
254
namespace Plugin\\${code};
255
256
use Eccube\\Common\\EccubeTwigBlock;
257
258
class TwigBlock implements EccubeTwigBlock
259
{
260
    /**
261
     * @return array
262
     */
263
    public static function getTwigBlock()
264
    {
265
        return [];
266
    }
267
}
268
269
EOL;
270
        $this->fs->dumpFile($pluginDir.'/TwigBlock.php', $source);
271
    }
272
273
    /**
274
     * @param string $pluginDir
275
     */
276
    protected function createNav($pluginDir, $code)
277
    {
278
        $source = <<<EOL
279
<?php
280
281
namespace Plugin\\${code};
282
283
use Eccube\\Common\\EccubeNav;
284
285
class Nav implements EccubeNav
286
{
287
    /**
288
     * @return array
289
     */
290
    public static function getNav()
291
    {
292
        return [];
293
    }
294
}
295
296
EOL;
297
        $this->fs->dumpFile($pluginDir.'/Nav.php', $source);
298
    }
299
300
    /**
301
     * @param string $pluginDir
302
     */
303
    protected function createEvent($pluginDir, $code)
304
    {
305
        $source = <<<EOL
306
<?php
307
308
namespace Plugin\\${code};
309
310
use Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;
311
312
class Event implements EventSubscriberInterface
313
{
314
    /**
315
     * @return array
316
     */
317
    public static function getSubscribedEvents()
318
    {
319
        return [];
320
    }
321
}
322
323
EOL;
324
        $this->fs->dumpFile($pluginDir.'/Event.php', $source);
325
    }
326
327
    /**
328
     * @param string $pluginDir
329
     */
330
    protected function createConfigController($pluginDir, $code)
331
    {
332
        $snakecased = Container::underscore($code);
333
334
        $source = <<<EOL
335
<?php
336
337
namespace Plugin\\${code}\\Controller\\Admin;
338
339
use Eccube\\Controller\\AbstractController;
340
use Plugin\\${code}\\Form\\Type\\Admin\\ConfigType;
341
use Plugin\\${code}\\Repository\\ConfigRepository;
342
use Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Template;
343
use Symfony\\Component\\HttpFoundation\\Request;
344
use Symfony\\Component\\Routing\\Annotation\\Route;
345
346
class ConfigController extends AbstractController
347
{
348
    /**
349
     * @var ConfigRepository
350
     */
351
    protected \$configRepository;
352
353
    /**
354
     * ConfigController constructor.
355
     *
356
     * @param ConfigRepository \$configRepository
357
     */
358
    public function __construct(ConfigRepository \$configRepository)
359
    {
360
        \$this->configRepository = \$configRepository;
361
    }
362
363
    /**
364
     * @Route("/%eccube_admin_route%/${snakecased}/config", name="${snakecased}_admin_config")
365
     * @Template("@${code}/admin/config.twig")
366
     */
367
    public function index(Request \$request)
368
    {
369
        \$Config = \$this->configRepository->get();
370
        \$form = \$this->createForm(ConfigType::class, \$Config);
371
        \$form->handleRequest(\$request);
372
373
        if (\$form->isSubmitted() && \$form->isValid()) {
374
            \$Config = \$form->getData();
375
            \$this->entityManager->persist(\$Config);
376
            \$this->entityManager->flush(\$Config);
377
            \$this->addSuccess('登録しました。', 'admin');
378
379
            return \$this->redirectToRoute('${snakecased}_admin_config');
380
        }
381
382
        return [
383
            'form' => \$form->createView(),
384
        ];
385
    }
386
}
387
388
EOL;
389
390
        $this->fs->dumpFile($pluginDir.'/Controller/Admin/ConfigController.php', $source);
391
392
        $source = <<<EOL
393
<?php
394
395
namespace Plugin\\${code}\\Entity;
396
397
use Doctrine\\ORM\\Mapping as ORM;
398
399
/**
400
 * Config
401
 *
402
 * @ORM\Table(name="plg_${snakecased}_config")
403
 * @ORM\Entity(repositoryClass="Plugin\\${code}\\Repository\\ConfigRepository")
404
 */
405
class Config
406
{
407
    /**
408
     * @var int
409
     *
410
     * @ORM\Column(name="id", type="integer", options={"unsigned":true})
411
     * @ORM\Id
412
     * @ORM\GeneratedValue(strategy="IDENTITY")
413
     */
414
    private \$id;
415
416
    /**
417
     * @var string
418
     *
419
     * @ORM\Column(name="name", type="string", length=255)
420
     */
421
    private \$name;
422
423
    /**
424
     * @return int
425
     */
426
    public function getId()
427
    {
428
        return \$this->id;
429
    }
430
431
    /**
432
     * @return string
433
     */
434
    public function getName()
435
    {
436
        return \$this->name;
437
    }
438
439
    /**
440
     * @param string \$name
441
     *
442
     * @return \$this;
443
     */
444
    public function setName(\$name)
445
    {
446
        \$this->name = \$name;
447
448
        return \$this;
449
    }
450
}
451
452
EOL;
453
454
        $this->fs->dumpFile($pluginDir.'/Entity/Config.php', $source);
455
456
        $source = <<<EOL
457
<?php
458
459
namespace Plugin\\${code}\\Repository;
460
461
use Eccube\\Repository\\AbstractRepository;
462
use Plugin\\${code}\\Entity\\Config;
463
use Symfony\\Bridge\\Doctrine\\RegistryInterface;
464
465
/**
466
 * ConfigRepository
467
 *
468
 * This class was generated by the Doctrine ORM. Add your own custom
469
 * repository methods below.
470
 */
471
class ConfigRepository extends AbstractRepository
472
{
473
    /**
474
     * ConfigRepository constructor.
475
     *
476
     * @param RegistryInterface \$registry
477
     */
478
    public function __construct(RegistryInterface \$registry)
479
    {
480
        parent::__construct(\$registry, Config::class);
481
    }
482
483
    /**
484
     * @param int \$id
485
     *
486
     * @return null|Config
487
     */
488
    public function get(\$id = 1)
489
    {
490
        return \$this->find(\$id);
491
    }
492
}
493
494
EOL;
495
496
        $this->fs->dumpFile($pluginDir.'/Repository/ConfigRepository.php', $source);
497
498
        $source = <<<EOL
499
<?php
500
501
namespace Plugin\\${code}\\Form\\Type\\Admin;
502
503
use Plugin\\${code}\\Entity\\Config;
504
use Symfony\\Component\\Form\\AbstractType;
505
use Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType;
506
use Symfony\\Component\\Form\\FormBuilderInterface;
507
use Symfony\\Component\\OptionsResolver\\OptionsResolver;
508
use Symfony\Component\Validator\Constraints\Length;
509
use Symfony\Component\Validator\Constraints\NotBlank;
510
511
class ConfigType extends AbstractType
512
{
513
    /**
514
     * {@inheritdoc}
515
     */
516
    public function buildForm(FormBuilderInterface \$builder, array \$options)
517
    {
518
        \$builder->add('name', TextType::class, [
519
            'constraints' => [
520
                new NotBlank(),
521
                new Length(['max' => 255]),
522
            ],
523
        ]);
524
    }
525
526
    /**
527
     * {@inheritdoc}
528
     */
529
    public function configureOptions(OptionsResolver \$resolver)
530
    {
531
        \$resolver->setDefaults([
532
            'data_class' => Config::class,
533
        ]);
534
    }
535
}
536
537
EOL;
538
539
        $this->fs->dumpFile($pluginDir.'/Form/Type/Admin/ConfigType.php', $source);
540
541
        $source = <<<EOL
542
{% extends '@admin/default_frame.twig' %}
543
544
{% set menus = ['store', 'plugin', 'plugin_list'] %}
545
546
{% block title %}${code}{% endblock %}
547
{% block sub_title %}プラグイン一覧{% endblock %}
548
549
{% form_theme form '@admin/Form/bootstrap_4_horizontal_layout.html.twig' %}
550
551
{% block stylesheet %}{% endblock stylesheet %}
552
553
{% block javascript %}{% endblock javascript %}
554
555
{% block main %}
556
    <form role="form" method="post">
557
558
        {{ form_widget(form._token) }}
559
560
        <div class="c-contentsArea__cols">
561
            <div class="c-contentsArea__primaryCol">
562
                <div class="c-primaryCol">
563
                    <div class="card rounded border-0 mb-4">
564
                        <div class="card-header"><span>設定</span></div>
565
                        <div class="card-body">
566
                            <div class="row">
567
                                <div class="col-3"><span>名前</span><span
568
                                            class="badge badge-primary ml-1">必須</span></div>
569
                                <div class="col mb-2">
570
                                    {{ form_widget(form.name) }}
571
                                    {{ form_errors(form.name) }}
572
                                </div>
573
                            </div>
574
                        </div>
575
                    </div>
576
                </div>
577
            </div>
578
        </div>
579
        <div class="c-conversionArea">
580
            <div class="c-conversionArea__container">
581
                <div class="row justify-content-between align-items-center">
582
                    <div class="col-6">
583
                        <div class="c-conversionArea__leftBlockItem">
584
                            <a class="c-baseLink"
585
                               href="{{ url('admin_store_plugin') }}">
586
                                <i class="fa fa-backward" aria-hidden="true"></i>
587
                                <span>プラグイン一覧</span>
588
                            </a>
589
                        </div>
590
                    </div>
591
                    <div class="col-6">
592
                        <div class="row align-items-center justify-content-end">
593
                            <div class="col-auto">
594
                                <button class="btn btn-ec-conversion px-5"
595
                                        type="submit">登録</button>
596
                            </div>
597
                        </div>
598
                    </div>
599
                </div>
600
            </div>
601
        </div>
602
    </form>
603
{% endblock %}
604
605
EOL;
606
        $this->fs->dumpFile($pluginDir.'/Resource/template/admin/config.twig', $source);
607
    }
608
}
609