Completed
Pull Request — 4.0 (#4581)
by NOBU
04:53
created

PluginGenerateCommand::createGithubActions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 35
rs 9.36
c 0
b 0
f 0
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) {
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) {
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) {
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