Completed
Pull Request — master (#139)
by Paul
04:46
created

ModuleCreateCommand::isValidTemplatingEngine()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4286
cc 1
eloc 7
nc 1
nop 1
1
<?php
2
/**
3
 * This file is part of the PPI Framework.
4
 *
5
 * @copyright  Copyright (c) 2011-2013 Paul Dragoonis <[email protected]>
6
 * @license    http://opensource.org/licenses/mit-license.php MIT
7
 *
8
 * @link       http://www.ppi.io
9
 */
10
11
namespace PPI\Framework\Console\Command;
12
13
use Symfony\Component\Console\Input\InputArgument;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Question\ChoiceQuestion;
18
19
/**
20
 * Module Command.
21
 *
22
 * @author      Paul Dragoonis <[email protected]>
23
 * @author      Vítor Brandão <[email protected]>
24
 */
25
class ModuleCreateCommand extends AbstractCommand
26
{
27
28
    const TPL_ENGINE_LATTE = 'latte';
29
    const TPL_ENGINE_PLATES = 'plates';
30
    const TPL_ENGINE_PHP = 'php';
31
    const TPL_ENGINE_TWIG = 'twig';
32
    const TPL_ENGINE_SMARTY = 'smarty';
33
34
    const ROUTING_ENGINE_SYMFONY = 'symfony';
35
    const ROUTING_ENGINE_AURA = 'aura';
36
    const ROUTING_ENGINE_LARAVEL = 'laravel';
37
    const ROUTING_ENGINE_FASTROUTE = 'fastroute';
38
39
    protected $skeletonModuleDir;
40
    protected $modulesDir;
41
    protected $tplEngine;
42
    protected $routingEngine;
43
    protected $configEnabledTemplatingEngines = [];
44
45
    /**
46
     * @var array
47
     */
48
    protected $coreDirs = [
49
        'src',
50
        'src/Controller',
51
        'tests',
52
        'resources',
53
        'resources/routes',
54
        'resources/config',
55
        'resources/views',
56
        'resources/views/index'
57
    ];
58
59
    /**
60
     * @var array
61
     */
62
    protected $coreFiles = [
63
        'Module.php',
64
        'resources/config/config.php'
65
    ];
66
67
    /**
68
     * @var array
69
     */
70
    protected $tplEngineFilesMap = [
71
        self::TPL_ENGINE_LATTE => [
72
            'resources/views/index/index.html.latte'
73
        ],
74
        self::TPL_ENGINE_PLATES => [
75
            'resources/views/index/index.html.plates'
76
        ],
77
        self::TPL_ENGINE_PHP => [
78
            'resources/views/index/index.html.php'
79
        ],
80
        self::TPL_ENGINE_TWIG => [
81
            'resources/views/index/base.html.twig',
82
            'resources/views/index/index.html.twig'
83
        ],
84
        self::TPL_ENGINE_SMARTY => [
85
            'resources/views/index/base.html.smarty',
86
            'resources/views/index/index.html.smarty'
87
        ]
88
    ];
89
90
    protected $routingEngineFilesMap = [
91
        self::ROUTING_ENGINE_SYMFONY => [
92
            'src/Controller/Index.php',
93
            'src/Controller/Shared.php',
94
            'resources/routes/symfony.yml'
95
        ],
96
        self::ROUTING_ENGINE_AURA => [
97
            'src/Controller/Index.php',
98
            'src/Controller/Shared.php',
99
            'resources/routes/aura.php'
100
        ],
101
        self::ROUTING_ENGINE_LARAVEL => [
102
            'src/Controller/Index.php',
103
            'src/Controller/Shared.php',
104
            'resources/routes/laravel.php'
105
        ],
106
        self::ROUTING_ENGINE_FASTROUTE => [
107
            'src/Controller/IndexInvoke.php', 
108
            'src/Controller/Shared.php',
109
            'resources/routes/fastroute.php'
110
        ],
111
    ];
112
113
    protected $routingEngineTokenMap = [
114
        self::ROUTING_ENGINE_SYMFONY => [
115
            '[ROUTING_LOAD_METHOD]' => 'loadSymfonyRoutes',
116
            '[ROUTING_DEF_FILE]' => 'symfony.yml',
117
            '[ROUTING_GETROUTES_RETVAL]' => '\Symfony\Component\Routing\RouteCollection'
118
        ],
119
        self::ROUTING_ENGINE_AURA => [
120
            '[ROUTING_LOAD_METHOD]' => 'loadAuraRoutes',
121
            '[ROUTING_DEF_FILE]' => 'aura.php',
122
            '[ROUTING_GETROUTES_RETVAL]' => '\Aura\Router\Router'
123
        ],
124
        self::ROUTING_ENGINE_LARAVEL => [
125
            '[ROUTING_LOAD_METHOD]' => 'loadLaravelRoutes',
126
            '[ROUTING_DEF_FILE]' => 'laravel.php',
127
            '[ROUTING_GETROUTES_RETVAL]' => '\Illuminate\Routing\Router'
128
        ],
129
        self::ROUTING_ENGINE_FASTROUTE => [
130
            '[ROUTING_LOAD_METHOD]' => 'loadFastRouteRoutes',
131
            '[ROUTING_DEF_FILE]' => 'fastroute.php',
132
            '[ROUTING_GETROUTES_RETVAL]' => '\PPI\FastRoute\Wrapper\FastRouteWrapper'
133
        ]
134
    ];
135
136
    /**
137
     * @param string $moduleDir
138
     */
139
    public function setSkeletonModuleDir($moduleDir)
140
    {
141
        $this->skeletonModuleDir = realpath($moduleDir);
142
    }
143
144
    /**
145
     * @param string $moduleDir
146
     */
147
    public function setTargetModuleDir($moduleDir)
148
    {
149
        $this->modulesDir = realpath($moduleDir);
150
    }
151
152
    /**
153
     * @param array $tplEngines
154
     */
155
    public function setEnabledTemplatingEngines(array $tplEngines)
156
    {
157
        $this->configEnabledTemplatingEngines = $tplEngines;
158
    }
159
160
161
    /**
162
     * @return void
163
     */
164
    protected function configure()
165
    {
166
        $this->setName('module:create')
167
            ->setDescription('Create a module')
168
            ->addArgument('name', InputArgument::REQUIRED, 'What is your module name?')
169
            ->addOption('dir', null, InputOption::VALUE_OPTIONAL, 'Specify the modules directory')
170
            ->addOption('tpl', null, InputOption::VALUE_OPTIONAL, 'Specify the templating engine')
171
            ->addOption('routing', null, InputOption::VALUE_OPTIONAL, 'Specify the routing engine');
172
    }
173
174
    /**
175
     * @param InputInterface $input
176
     * @param OutputInterface $output
177
     * @throws \Exception
178
     *
179
     * @return void
180
     */
181
    protected function execute(InputInterface $input, OutputInterface $output)
182
    {
183
184
        $moduleName = $input->getArgument('name');
185
        $moduleDir = $this->modulesDir . DIRECTORY_SEPARATOR . $moduleName;
186
187
        // Acquire Module Information
188
        $this->askQuestions($input, $output);
189
        $this->createModuleStructure($moduleDir, $moduleName);
190
        // Copy the core files
191
        $this->copyFiles($this->skeletonModuleDir, $moduleDir, $this->coreFiles);
192
193
        $tokenizedFiles = [];
194
        $tokens = [];
195
        foreach ($this->coreFiles as $coreFile) {
196
            $tokenizedFiles[] = $coreFile;
197
        }
198
199
        if(!$this->isValidTemplatingEngine($this->tplEngine)) {
200
            throw new \Exception('Invalid templating engine: ' . $this->tplEngine);
201
        }
202
203
        // TEMPLATING
204
205
        // Copy templating files over
206
        $tplFiles = $this->tplEngineFilesMap[$this->tplEngine];
207
        $this->copyFiles($this->skeletonModuleDir, $moduleDir, $tplFiles);
208
209
        // Setting up templating tokens
210
        foreach ($tplFiles as $tplFile) {
211
            $tokenizedFiles[] = $tplFile;
212
        }
213
214
        $tokens['[MODULE_NAME]'] = $moduleName;
215
        $tokens['[TPL_ENGINE_EXT]'] = $this->tplEngine;
216
217
        // ROUTING
218
        if(!$this->isValidRoutingEngine($this->routingEngine)) {
219
            throw new \Exception('Invalid routing engine: ' . $this->routingEngine);
220
        }
221
222
        // Copy routing files over
223
        $routingFiles = $this->routingEngineFilesMap[$this->routingEngine];
224
        $this->copyFiles($this->skeletonModuleDir, $moduleDir, $routingFiles);
225
226
        // Setting up routing tokens
227
        foreach ($routingFiles as $routingFile) {
228
            $tokenizedFiles[] = $routingFile;
229
        }
230
        $routingTokensMap = $this->routingEngineTokenMap[$this->routingEngine];
231
        foreach ($routingTokensMap as $routingTokenKey => $routingTokenVal) {
232
            $tokens[$routingTokenKey] = $routingTokenVal;
233
        }
234
235
        // Replace tokens in all files
236
        $this->replaceTokensInFiles($moduleDir, $tokenizedFiles, $tokens);
237
238
239
        if($this->routingEngine === self::ROUTING_ENGINE_FASTROUTE) {
240
            rename(
241
                $moduleDir . DIRECTORY_SEPARATOR . $routingFiles[0],
242
                str_replace('IndexInvoke', 'Index', $moduleDir . DIRECTORY_SEPARATOR . $routingFiles[0]
243
           ));
244
        }
245
246
        // Success
247
        $output->writeln("<info>Created module successfully</info>");
248
        $output->writeln("Name: <info>{$moduleName}</info>");
249
        $output->writeln(sprintf("Routing: <info>%s</info>", $this->routingEngine));
250
        $output->writeln(sprintf("Templating: <info>%s</info>", $this->tplEngine));
251
        $output->writeln(sprintf("Path: <info>%s</info>", $moduleDir));
252
253
        $output->writeln("<comment>This module is not enabled. Enable it in <info>config[modules]</info> key</comment>");
254
255
        $this->checkTemplatingEngines($input, $output);
256
        $this->checkRouters($input, $output);
257
258
259
    }
260
261
    protected function isValidTemplatingEngine($tplEngine)
262
    {
263
        return in_array($tplEngine, [
264
            self::TPL_ENGINE_LATTE,
265
            self::TPL_ENGINE_PLATES,
266
            self::TPL_ENGINE_PHP,
267
            self::TPL_ENGINE_SMARTY,
268
            self::TPL_ENGINE_TWIG
269
        ]);
270
    }
271
272
    protected function isValidRoutingEngine($routingEngine)
273
    {
274
        return in_array($routingEngine, [
275
            self::ROUTING_ENGINE_SYMFONY,
276
            self::ROUTING_ENGINE_AURA,
277
            self::ROUTING_ENGINE_LARAVEL,
278
            self::ROUTING_ENGINE_FASTROUTE
279
        ]);
280
    }
281
282
    /**
283
     * @param string $moduleDir
284
     * @param array $files
285
     * @param array $tokens
286
     *
287
     * @return void
288
     */
289
    protected function replaceTokensInFiles($moduleDir, $files, $tokens)
290
    {
291
        foreach ($files as $file) {
292
            $file = $moduleDir . DIRECTORY_SEPARATOR . $file;
293
            if (!is_writeable($file)) {
294
                throw new \InvalidArgumentException(sprintf('File %s is not writeable', $file));
295
            }
296
            file_put_contents($file, str_replace(array_keys($tokens), array_values($tokens), file_get_contents($file)));
297
        }
298
    }
299
300
    /**
301
     * @param string $skeletonDir
302
     * @param string $moduleDir
303
     * @param array $files
304
     *
305
     * @throws \InvalidArgumentException When a file path being created already exists
306
     */
307
    protected function copyFiles($skeletonDir, $moduleDir, $files)
308
    {
309
        foreach ($files as $file) {
310
            $srcFile = $skeletonDir . DIRECTORY_SEPARATOR . $file;
311
            $dstFile = $moduleDir . DIRECTORY_SEPARATOR . $file;
312
            if (!file_exists($srcFile)) {
313
                throw new \InvalidArgumentException(sprintf('File does not exist: %s', $srcFile));
314
            }
315
            if (file_exists($dstFile)) {
316
                throw new \InvalidArgumentException(sprintf('File already exists: %s', $dstFile));
317
            }
318
            copy($srcFile, $dstFile);
319
        }
320
    }
321
322
    /**
323
     * @param string $moduleDir
324
     * @param string $moduleName
325
     *
326
     * @throws \InvalidArgumentException When a dir path being created already exists
327
     */
328
    protected function createModuleStructure($moduleDir, $moduleName)
329
    {
330
        if (is_dir($moduleDir)) {
331
            throw new \InvalidArgumentException(sprintf('Unable to create module: %s it already exists at %s%s', $moduleName, $moduleDir, $moduleName));
332
        }
333
334
        @mkdir($moduleDir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
335
336
        // Create base structure
337
        foreach ($this->coreDirs as $coreDir) {
338
            $tmpDir = $moduleDir . DIRECTORY_SEPARATOR . $coreDir;
339
            @mkdir($tmpDir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
340
        }
341
    }
342
343
    /**
344
     * @param InputInterface $input
345
     * @param OutputInterface $output
346
     */
347
    protected function askQuestions(InputInterface $input, OutputInterface $output)
348
    {
349
        // Module DIR
350
        if ($input->getOption('dir') == null) {
351
            $questionHelper = $this->getHelper('question');
352
            $modulesDirQuestion = new ChoiceQuestion('Where is the modules dir?', [1 => $this->modulesDir], $this->modulesDir);
353
            $modulesDirQuestion->setErrorMessage('Modules dir: %s is invalid.');
354
            $this->modulesDir = $questionHelper->ask($input, $output, $modulesDirQuestion);
355
        }
356
357
        // Templating
358
        if ($input->getOption('tpl') == null) {
359
            $questionHelper = $this->getHelper('question');
360
            $tplQuestion = new ChoiceQuestion('Choose your templating engine [php]', [1 => 'php', 2 => 'twig', 3 => 'smarty', 4 => 'plates', 5 => 'latte'], 'php');
361
            $tplQuestion->setErrorMessage('Templating engine %s is invalid.');
362
            $this->tplEngine = $questionHelper->ask($input, $output, $tplQuestion);
363
        }
364
        // Routing
365
        if ($input->getOption('routing') == null) {
366
            $questionHelper = $this->getHelper('question');
367
            $routingQuestion = new ChoiceQuestion('Choose your routing engine [symfony]',
368
                [
369
                    1 => self::ROUTING_ENGINE_SYMFONY,
370
                    2 => self::ROUTING_ENGINE_AURA,
371
                    3 => self::ROUTING_ENGINE_LARAVEL,
372
                    4 => self::ROUTING_ENGINE_FASTROUTE
373
                ],
374
                'symfony'
375
            );
376
            $tplQuestion->setErrorMessage('Routing engine %s is invalid.');
0 ignored issues
show
Bug introduced by
The variable $tplQuestion does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
377
            $this->routingEngine = $questionHelper->ask($input, $output, $routingQuestion);
378
        }
379
    }
380
381
    /**
382
     * @param InputInterface $input
383
     * @param OutputInterface $output
384
     */
385
    private function checkRouters(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
386
    {
387
        // Aura Check
388
        if($this->routingEngine == self::ROUTING_ENGINE_AURA && !class_exists('\Aura\Router\Router')) {
389
            $output->writeln("<comment>Aura Router doesn't appear to be loaded. Run: <info>composer require ppi/aura-router</info></comment>");
390
        }
391
392
        // Laravel check
393
        if($this->routingEngine == self::ROUTING_ENGINE_LARAVEL && !class_exists('\PPI\LaravelRouting\LaravelRouter')) {
394
            $output->writeln("<comment>Laravel Router doesn't appear to be loaded. Run: <info>composer require ppi/laravel-router</info></comment>");
395
        }
396
397
        if($this->routingEngine == self::ROUTING_ENGINE_FASTROUTE && !class_exists('\PPI\FastRoute\Wrapper\FastRouteWrapper')) {
398
            $output->writeln("<comment>FastRoute Router doesn't appear to be loaded. Run: <info>composer require ppi/fast-route</info></comment>");
399
        }
400
401
    }
402
403
    /**
404
     * @param InputInterface $input
405
     * @param OutputInterface $output
406
     */
407
    private function checkTemplatingEngines(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
408
    {
409
        // PHP Templating Engine checks
410
        if ($this->tplEngine == self::TPL_ENGINE_PHP) {
411
            if (!in_array($this->tplEngine, $this->configEnabledTemplatingEngines)) {
412
                $output->writeln(sprintf("<comment>PHP is not an enabled templating engine. Add <info>%s</info> it in <info>config[framework][templating][engines]</info> key</comment>", $this->tplEngine));
413
            }
414
        }
415
416
        // Twig Checks
417 View Code Duplication
        if ($this->tplEngine == self::TPL_ENGINE_TWIG) {
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...
418
            if (!in_array($this->tplEngine, $this->configEnabledTemplatingEngines)) {
419
                $output->writeln(sprintf("<comment>Twig is not an enabled templating engine. Add <info>%s</info> it in <info>config[framework][templating][engines]</info> key</comment>", $this->tplEngine));
420
            }
421
            if (!class_exists('\Twig_Environment')) {
422
                $output->writeln("<comment>Twig doesn't appear to be loaded. Run: <info>composer require ppi/twig-module</info></comment>");
423
            }
424
        }
425
426
        // Smarty Checks
427 View Code Duplication
        if ($this->tplEngine == self::TPL_ENGINE_SMARTY) {
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...
428
            if (!in_array($this->tplEngine, $this->configEnabledTemplatingEngines)) {
429
                $output->writeln(sprintf("<comment>Smarty is not an enabled templating engine. Add <info>%s</info> it in <info>config[framework][templating][engines]</info> key</comment>", $this->tplEngine));
430
            }
431
            if (!class_exists('\Smarty')) {
432
                $output->writeln("<comment>Smarty doesn't appear to be loaded. Run: <info>composer require ppi/smarty-module</info></comment>");
433
            }
434
        }
435
436
        // Plates Checks
437 View Code Duplication
        if ($this->tplEngine == self::TPL_ENGINE_PLATES) {
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...
438
            if (!in_array($this->tplEngine, $this->configEnabledTemplatingEngines)) {
439
                $output->writeln(sprintf("<comment>Plates is not an enabled templating engine. Add <info>%s</info> it in <info>config[framework][templating][engines]</info> key</comment>", $this->tplEngine));
440
            }
441
            if (!class_exists('\PPI\PlatesModule\Wrapper\PlatesWrapper')) {
442
                $output->writeln("<comment>Plates doesn't appear to be loaded. Run: <info>composer require ppi/plates-module</info></comment>");
443
            }
444
        }
445
446
        // Plates Checks
447 View Code Duplication
        if ($this->tplEngine == self::TPL_ENGINE_LATTE) {
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...
448
            if (!in_array($this->tplEngine, $this->configEnabledTemplatingEngines)) {
449
                $output->writeln(sprintf("<comment>Latte is not an enabled templating engine. Add <info>%s</info> it in <info>config[framework][templating][engines]</info> key</comment>", $this->tplEngine));
450
            }
451
            if (!class_exists('\PPI\LatteModule\Wrapper\LatteWrapper')) {
452
                $output->writeln("<comment>Latte doesn't appear to be loaded. Run: <info>composer require ppi/latte-module</info></comment>");
453
            }
454
        }
455
456
    }
457
}
458