MakeCommand   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 794
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 6
dl 0
loc 794
ccs 282
cts 282
cp 1
rs 8.686
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 4 1
A getDescription() 0 4 1
A getHelp() 0 4 1
A getArguments() 0 25 1
A getOptions() 0 4 1
A execute() 0 4 1
B run() 0 87 7
A composeMigration() 0 21 1
A composeSeed() 0 21 1
A composeModel() 0 24 1
A composeSchema() 0 21 1
A composeApi() 0 23 1
A composeAuthorization() 0 25 1
A composeValidationRules() 0 24 1
A composeJsonValidationOnCreateRules() 0 21 1
A composeJsonValidationOnUpdateRules() 0 21 1
A composeQueryValidationOnReadRules() 0 22 1
A composeJsonController() 0 22 1
A composeJsonRoute() 0 21 1
A composeWebValidationOnCreateRules() 0 20 1
A composeWebValidationOnUpdateRules() 0 20 1
A composeWebController() 0 24 1
A composeWebRoute() 0 21 1
B createTemplates() 0 36 9
A composeTemplateContent() 0 13 2
A isValidShortClassName() 0 4 2
A getTemplatePath() 0 4 1
A filterOutFolderMask() 0 9 1

How to fix   Complexity   

Complex Class

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

1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Application\Commands;
4
5
/**
6
 * Copyright 2015-2020 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Application\Exceptions\InvalidArgumentException;
22
use Limoncello\Contracts\Commands\CommandInterface;
23
use Limoncello\Contracts\Commands\IoInterface;
24
use Limoncello\Contracts\FileSystem\FileSystemInterface;
25
use Limoncello\Contracts\Settings\Packages\AuthorizationSettingsInterface;
26
use Limoncello\Contracts\Settings\Packages\DataSettingsInterface;
27
use Limoncello\Contracts\Settings\Packages\FluteSettingsInterface;
28
use Limoncello\Contracts\Settings\SettingsProviderInterface;
29
use Psr\Container\ContainerExceptionInterface;
30
use Psr\Container\ContainerInterface;
31
use Psr\Container\NotFoundExceptionInterface;
32
use function array_merge;
33
use function implode;
34
use function preg_match;
35
use function str_replace;
36
use function strtolower;
37
use function strtoupper;
38
39
/**
40
 * @package Limoncello\Application
41
 *
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 */
44
class MakeCommand implements CommandInterface
45
{
46
    /**
47
     * Command name.
48
     */
49
    const NAME = 'l:make';
50
51
    /** Argument name */
52
    const ARG_ITEM = 'item';
53
54
    /** Argument name */
55
    const ARG_SINGULAR = 'singular';
56
57
    /** Argument name */
58
    const ARG_PLURAL = 'plural';
59
60
    /** Command action */
61
    const ITEM_DATA_RESOURCE = 'data-resource';
62
63
    /** Command action */
64
    const ITEM_WEB_RESOURCE = 'web-resource';
65
66
    /** Command action */
67
    const ITEM_JSON_API_RESOURCE = 'json-resource';
68
69
    /** Command action */
70
    const ITEM_FULL_RESOURCE = 'resource';
71
72 1
    /**
73
     * Taken from http://php.net/manual/en/language.oop5.basic.php
74 1
     */
75
    protected const VALID_CLASS_NAME_REGEX = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
76
77
    /**
78
     * @inheritdoc
79
     */
80 1
    public static function getName(): string
81
    {
82 1
        return static::NAME;
83
    }
84
85
    /**
86
     * @inheritdoc
87
     */
88 1
    public static function getDescription(): string
89
    {
90 1
        return 'Creates necessary classes for models, migrations and data seeds.';
91
    }
92
93
    /**
94
     * @inheritdoc
95
     */
96 1
    public static function getHelp(): string
97
    {
98 1
        return 'This command creates necessary classes for models, migrations and data seeds.';
99 1
    }
100 1
101 1
    /**
102
     * @inheritdoc
103
     */
104
    public static function getArguments(): array
105 1
    {
106 1
        $data     = static::ITEM_DATA_RESOURCE;
107 1
        $web      = static::ITEM_WEB_RESOURCE;
108
        $json     = static::ITEM_JSON_API_RESOURCE;
109
        $resource = static::ITEM_FULL_RESOURCE;
110 1
111 1
        return [
112 1
            [
113
                static::ARGUMENT_NAME        => static::ARG_ITEM,
114
                static::ARGUMENT_DESCRIPTION => "Action such as `$data`, `$web`, `$json` or `$resource`.",
115 1
                static::ARGUMENT_MODE        => static::ARGUMENT_MODE__REQUIRED,
116 1
            ],
117 1
            [
118
                static::ARGUMENT_NAME        => static::ARG_SINGULAR,
119
                static::ARGUMENT_DESCRIPTION => 'Singular name in camel case (e.g. `Post`).',
120
                static::ARGUMENT_MODE        => static::ARGUMENT_MODE__REQUIRED,
121
            ],
122
            [
123
                static::ARGUMENT_NAME        => static::ARG_PLURAL,
124
                static::ARGUMENT_DESCRIPTION => 'Plural name in camel case (e.g. `Posts`).',
125 1
                static::ARGUMENT_MODE        => static::ARGUMENT_MODE__REQUIRED,
126
            ],
127 1
        ];
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133 11
    public static function getOptions(): array
134
    {
135 11
        return [];
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141
    public static function execute(ContainerInterface $container, IoInterface $inOut): void
142
    {
143
        (new static())->run($container, $inOut);
144
    }
145
146
    /**
147 11
     * @param ContainerInterface $container
148
     * @param IoInterface        $inOut
149 11
     *
150 11
     * @return void
151 11
     *
152
     * @throws ContainerExceptionInterface
153 11
     * @throws NotFoundExceptionInterface
154 1
     */
155
    protected function run(ContainerInterface $container, IoInterface $inOut): void
156 10
    {
157 1
        $item     = $inOut->getArguments()[static::ARG_ITEM];
158
        $singular = $inOut->getArguments()[static::ARG_SINGULAR];
159
        $plural   = $inOut->getArguments()[static::ARG_PLURAL];
160
161 9
        if ($this->isValidShortClassName($singular) === false) {
162
            throw new InvalidArgumentException("`$singular` is not a valid class name.");
163 9
        }
164
        if ($this->isValidShortClassName($plural) === false) {
165
            throw new InvalidArgumentException("`$plural` is not a valid class name.");
166
        }
167 8
168 8
        /** @var FileSystemInterface $fileSystem */
169 8
        $fileSystem = $container->get(FileSystemInterface::class);
170
        /** @var SettingsProviderInterface $settingsProvider */
171 9
        $settingsProvider = $container->get(SettingsProviderInterface::class);
172
173
        $dataTemplates = function () use ($settingsProvider, $fileSystem, $singular, $plural) : array {
174
            return [
175 3
                $this->composeMigration($settingsProvider, $fileSystem, $singular, $plural),
176 3
                $this->composeSeed($settingsProvider, $fileSystem, $singular, $plural),
177 3
                $this->composeModel($settingsProvider, $fileSystem, $singular, $plural),
178 3
            ];
179 3
        };
180
181 9
        $basicTemplates = function () use ($settingsProvider, $fileSystem, $singular, $plural) : array {
182
            return [
183
                $this->composeSchema($settingsProvider, $fileSystem, $singular, $plural),
184
                $this->composeAuthorization($settingsProvider, $fileSystem, $singular, $plural),
185 2
                $this->composeApi($settingsProvider, $fileSystem, $singular, $plural),
186 2
                $this->composeValidationRules($settingsProvider, $fileSystem, $singular, $plural),
187 2
                $this->composeQueryValidationOnReadRules($settingsProvider, $fileSystem, $singular, $plural),
188 2
            ];
189
        };
190 9
191
        $webTemplates = function () use ($settingsProvider, $fileSystem, $singular, $plural) : array {
192
            return [
193
                $this->composeWebValidationOnCreateRules($settingsProvider, $fileSystem, $singular),
194 2
                $this->composeWebValidationOnUpdateRules($settingsProvider, $fileSystem, $singular),
195 2
                $this->composeWebController($settingsProvider, $fileSystem, $singular, $plural),
196 2
                $this->composeWebRoute($settingsProvider, $fileSystem, $singular, $plural),
197 2
            ];
198
        };
199 9
200
        $jsonTemplates = function () use ($settingsProvider, $fileSystem, $singular, $plural) : array {
201
            return [
202 9
                $this->composeJsonValidationOnCreateRules($settingsProvider, $fileSystem, $singular),
203 5
                $this->composeJsonValidationOnUpdateRules($settingsProvider, $fileSystem, $singular),
204 5
                $this->composeJsonController($settingsProvider, $fileSystem, $singular, $plural),
205
                $this->composeJsonRoute($settingsProvider, $fileSystem, $singular, $plural),
206 1
            ];
207 4
        };
208 1
209 1
        switch ($item) {
210 1
            case static::ITEM_DATA_RESOURCE:
211 1
                $this->createTemplates($fileSystem, array_merge(
212
                    $dataTemplates()
213 1
                ));
214 3
                break;
215 1
            case static::ITEM_WEB_RESOURCE:
216 1
                $this->createTemplates($fileSystem, array_merge(
217 1
                    $dataTemplates(),
218 1
                    $basicTemplates(),
219
                    $webTemplates()
220 1
                ));
221 2
                break;
222 1
            case static::ITEM_JSON_API_RESOURCE:
223 1
                $this->createTemplates($fileSystem, array_merge(
224 1
                    $dataTemplates(),
225 1
                    $basicTemplates(),
226 1
                    $jsonTemplates()
227
                ));
228 1
                break;
229
            case static::ITEM_FULL_RESOURCE:
230 1
                $this->createTemplates($fileSystem, array_merge(
231 1
                    $dataTemplates(),
232
                    $basicTemplates(),
233
                    $webTemplates(),
234
                    $jsonTemplates()
235
                ));
236
                break;
237
            default:
238
                $inOut->writeError("Unsupported item type `$item`." . PHP_EOL);
239
                break;
240
        }
241
    }
242
243 8
    /**
244
     * @param SettingsProviderInterface $settingsProvider
245
     * @param FileSystemInterface       $fileSystem
246
     * @param string                    $singular
247
     * @param string                    $plural
248
     *
249 8
     * @return TemplateOutput
250
     */
251 8
    private function composeMigration(
252 8
        SettingsProviderInterface $settingsProvider,
253 8
        FileSystemInterface $fileSystem,
254 8
        string $singular,
255 8
        string $plural
256
    ): TemplateOutput {
257 8
        $folder = $settingsProvider->get(DataSettingsInterface::class)[DataSettingsInterface::KEY_MIGRATIONS_FOLDER];
258 8
259
        $outputRootFolder = $this->filterOutFolderMask($folder);
260
        $outputFileName   = $plural . 'Migration.php';
261
        $outputContent    = $this->composeTemplateContent(
262 8
            $fileSystem,
263
            $this->getTemplatePath('Migration.txt'),
264
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...LURAL_CC%}' => $plural) is of type array<string,string,{"{%...PLURAL_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
265
                '{%SINGULAR_CC%}' => $singular,
266
                '{%PLURAL_CC%}'   => $plural,
267
            ]
268
        );
269
270
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
271
    }
272
273 8
    /**
274
     * @param SettingsProviderInterface $settingsProvider
275
     * @param FileSystemInterface       $fileSystem
276
     * @param string                    $singular
277
     * @param string                    $plural
278
     *
279 8
     * @return TemplateOutput
280
     */
281 8
    private function composeSeed(
282 8
        SettingsProviderInterface $settingsProvider,
283 8
        FileSystemInterface $fileSystem,
284 8
        string $singular,
285 8
        string $plural
286
    ): TemplateOutput {
287 8
        $folder = $settingsProvider->get(DataSettingsInterface::class)[DataSettingsInterface::KEY_SEEDS_FOLDER];
288 8
289
        $outputRootFolder = $this->filterOutFolderMask($folder);
290
        $outputFileName   = $plural . 'Seed.php';
291
        $outputContent    = $this->composeTemplateContent(
292 8
            $fileSystem,
293
            $this->getTemplatePath('Seed.txt'),
294
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...LURAL_CC%}' => $plural) is of type array<string,string,{"{%...PLURAL_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
295
                '{%SINGULAR_CC%}' => $singular,
296
                '{%PLURAL_CC%}'   => $plural,
297
            ]
298
        );
299
300
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
301
    }
302
303 8
    /**
304
     * @param SettingsProviderInterface $settingsProvider
305
     * @param FileSystemInterface       $fileSystem
306
     * @param string                    $singular
307
     * @param string                    $plural
308
     *
309 8
     * @return TemplateOutput
310
     */
311 8
    private function composeModel(
312 8
        SettingsProviderInterface $settingsProvider,
313 8
        FileSystemInterface $fileSystem,
314 8
        string $singular,
315 8
        string $plural
316
    ): TemplateOutput {
317 8
        $folder = $settingsProvider->get(DataSettingsInterface::class)[DataSettingsInterface::KEY_MODELS_FOLDER];
318 8
319 8
        $outputRootFolder = $this->filterOutFolderMask($folder);
320 8
        $outputFileName   = $singular . '.php';
321 8
        $outputContent    = $this->composeTemplateContent(
322
            $fileSystem,
323
            $this->getTemplatePath('Model.txt'),
324
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...> \strtoupper($plural)) is of type array<string,string,{"{%...PLURAL_UC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
325 8
                '{%SINGULAR_CC%}' => $singular,
326
                '{%SINGULAR_LC%}' => strtolower($singular),
327
                '{%SINGULAR_UC%}' => strtoupper($singular),
328
                '{%PLURAL_LC%}'   => strtolower($plural),
329
                '{%PLURAL_UC%}'   => strtoupper($plural),
330
            ]
331
        );
332
333
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
334
    }
335
336 3
    /**
337
     * @param SettingsProviderInterface $settingsProvider
338
     * @param FileSystemInterface       $fileSystem
339
     * @param string                    $singular
340
     * @param string                    $plural
341
     *
342 3
     * @return TemplateOutput
343
     */
344 3
    private function composeSchema(
345 3
        SettingsProviderInterface $settingsProvider,
346 3
        FileSystemInterface $fileSystem,
347 3
        string $singular,
348 3
        string $plural
349
    ): TemplateOutput {
350 3
        $folder = $settingsProvider->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_SCHEMAS_FOLDER];
351 3
352
        $outputRootFolder = $this->filterOutFolderMask($folder);
353
        $outputFileName   = $singular . 'Schema.php';
354
        $outputContent    = $this->composeTemplateContent(
355 3
            $fileSystem,
356
            $this->getTemplatePath('Schema.txt'),
357
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...> \strtolower($plural)) is of type array<string,string,{"{%...PLURAL_LC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
358
                '{%SINGULAR_CC%}' => $singular,
359
                '{%PLURAL_LC%}'   => strtolower($plural),
360
            ]
361
        );
362
363
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
364
    }
365
366 3
    /**
367
     * @param SettingsProviderInterface $settingsProvider
368
     * @param FileSystemInterface       $fileSystem
369
     * @param string                    $singular
370
     * @param string                    $plural
371
     *
372 3
     * @return TemplateOutput
373
     */
374 3
    private function composeApi(
375 3
        SettingsProviderInterface $settingsProvider,
376 3
        FileSystemInterface $fileSystem,
377 3
        string $singular,
378 3
        string $plural
379
    ): TemplateOutput {
380 3
        $folder = $settingsProvider->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_API_FOLDER];
381 3
382 3
        $outputRootFolder = $this->filterOutFolderMask($folder);
383 3
        $outputFileName   = $plural . 'Api.php';
384
        $outputContent    = $this->composeTemplateContent(
385
            $fileSystem,
386
            $this->getTemplatePath('Api.txt'),
387 3
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...> \strtoupper($plural)) is of type array<string,string,{"{%...PLURAL_UC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
388
                '{%SINGULAR_CC%}' => $singular,
389
                '{%PLURAL_CC%}'   => $plural,
390
                '{%SINGULAR_UC%}' => strtoupper($singular),
391
                '{%PLURAL_UC%}'   => strtoupper($plural),
392
            ]
393
        );
394
395
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
396
    }
397
398 3
    /**
399
     * @param SettingsProviderInterface $settingsProvider
400
     * @param FileSystemInterface       $fileSystem
401
     * @param string                    $singular
402
     * @param string                    $plural
403
     *
404
     * @return TemplateOutput
405 3
     */
406
    private function composeAuthorization(
407 3
        SettingsProviderInterface $settingsProvider,
408 3
        FileSystemInterface $fileSystem,
409 3
        string $singular,
410 3
        string $plural
411 3
    ): TemplateOutput {
412
        $folder = $settingsProvider
413 3
                      ->get(AuthorizationSettingsInterface::class)[AuthorizationSettingsInterface::KEY_POLICIES_FOLDER];
414 3
415 3
        $outputRootFolder = $this->filterOutFolderMask($folder);
416 3
        $outputFileName   = $singular . 'Rules.php';
417 3
        $outputContent    = $this->composeTemplateContent(
418
            $fileSystem,
419
            $this->getTemplatePath('ApiAuthorization.txt'),
420
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...\strtoupper($singular)) is of type array<string,string,{"{%...NGULAR_UC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
421 3
                '{%SINGULAR_CC%}' => $singular,
422
                '{%PLURAL_CC%}'   => $plural,
423
                '{%SINGULAR_LC%}' => strtolower($singular),
424
                '{%PLURAL_UC%}'   => strtoupper($plural),
425
                '{%SINGULAR_UC%}' => strtoupper($singular),
426
            ]
427
        );
428
429
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
430
    }
431
432 3
    /**
433
     * @param SettingsProviderInterface $settingsProvider
434
     * @param FileSystemInterface       $fileSystem
435
     * @param string                    $singular
436
     * @param string                    $plural
437
     *
438
     * @return TemplateOutput
439 3
     */
440
    private function composeValidationRules(
441 3
        SettingsProviderInterface $settingsProvider,
442 3
        FileSystemInterface $fileSystem,
443 3
        string $singular,
444 3
        string $plural
445 3
    ): TemplateOutput {
446
        $folder = $settingsProvider
447 3
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_VALIDATION_RULES_FOLDER];
448 3
449 3
        $outputRootFolder = $this->filterOutFolderMask($folder);
450 3
        $outputFileName   = $singular . 'Rules.php';
451
        $outputContent    = $this->composeTemplateContent(
452
            $fileSystem,
453
            $this->getTemplatePath('ValidationRules.txt'),
454 3
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...> \strtolower($plural)) is of type array<string,string,{"{%...PLURAL_LC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
455
                '{%SINGULAR_CC%}' => $singular,
456
                '{%PLURAL_CC%}'   => $plural,
457
                '{%SINGULAR_LC%}' => strtolower($singular),
458
                '{%PLURAL_LC%}'   => strtolower($plural),
459
            ]
460
        );
461
462
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent, $singular);
463
    }
464 2
465
    /**
466
     * @param SettingsProviderInterface $settingsProvider
467
     * @param FileSystemInterface       $fileSystem
468
     * @param string                    $singular
469
     *
470 2
     * @return TemplateOutput
471
     */
472 2
    private function composeJsonValidationOnCreateRules(
473 2
        SettingsProviderInterface $settingsProvider,
474 2
        FileSystemInterface $fileSystem,
475 2
        string $singular
476 2
    ): TemplateOutput {
477
        $folder = $settingsProvider
478 2
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_VALIDATORS_FOLDER];
479 2
480
        $outputRootFolder = $this->filterOutFolderMask($folder);
481
        $outputFileName   = $singular . 'CreateJson.php';
482
        $outputContent    = $this->composeTemplateContent(
483 2
            $fileSystem,
484
            $this->getTemplatePath('JsonRulesOnCreate.txt'),
485
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...\strtolower($singular)) is of type array<string,string,{"{%...NGULAR_LC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
486
                '{%SINGULAR_CC%}' => $singular,
487
                '{%SINGULAR_LC%}' => strtolower($singular),
488
            ]
489
        );
490
491
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent, $singular);
492
    }
493 2
494
    /**
495
     * @param SettingsProviderInterface $settingsProvider
496
     * @param FileSystemInterface       $fileSystem
497
     * @param string                    $singular
498
     *
499 2
     * @return TemplateOutput
500
     */
501 2
    private function composeJsonValidationOnUpdateRules(
502 2
        SettingsProviderInterface $settingsProvider,
503 2
        FileSystemInterface $fileSystem,
504 2
        string $singular
505 2
    ): TemplateOutput {
506
        $folder = $settingsProvider
507 2
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_VALIDATORS_FOLDER];
508 2
509
        $outputRootFolder = $this->filterOutFolderMask($folder);
510
        $outputFileName   = $singular . 'UpdateJson.php';
511
        $outputContent    = $this->composeTemplateContent(
512 2
            $fileSystem,
513
            $this->getTemplatePath('JsonRulesOnUpdate.txt'),
514
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...\strtolower($singular)) is of type array<string,string,{"{%...NGULAR_LC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
515
                '{%SINGULAR_CC%}' => $singular,
516
                '{%SINGULAR_LC%}' => strtolower($singular),
517
            ]
518
        );
519
520
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent, $singular);
521
    }
522
523
    /**
524 3
     * @param SettingsProviderInterface $settingsProvider
525
     * @param FileSystemInterface       $fileSystem
526
     * @param string                    $singular
527
     * @param string                    $plural
528
     *
529
     * @return TemplateOutput
530
     *
531 3
     */
532
    private function composeQueryValidationOnReadRules(
533 3
        SettingsProviderInterface $settingsProvider,
534 3
        FileSystemInterface $fileSystem,
535 3
        string $singular,
536 3
        string $plural
537 3
    ): TemplateOutput {
538
        $folder = $settingsProvider
539 3
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_VALIDATORS_FOLDER];
540 3
541
        $outputRootFolder = $this->filterOutFolderMask($folder);
542
        $outputFileName   = $plural . 'ReadQuery.php';
543
        $outputContent    = $this->composeTemplateContent(
544 3
            $fileSystem,
545
            $this->getTemplatePath('QueryRulesOnRead.txt'),
546
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...LURAL_CC%}' => $plural) is of type array<string,string,{"{%...PLURAL_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
547
                '{%SINGULAR_CC%}' => $singular,
548
                '{%PLURAL_CC%}'   => $plural,
549
            ]
550
        );
551
552
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent, $singular);
553
    }
554
555 2
    /**
556
     * @param SettingsProviderInterface $settingsProvider
557
     * @param FileSystemInterface       $fileSystem
558
     * @param string                    $singular
559
     * @param string                    $plural
560
     *
561
     * @return TemplateOutput
562 2
     */
563
    private function composeJsonController(
564 2
        SettingsProviderInterface $settingsProvider,
565 2
        FileSystemInterface $fileSystem,
566 2
        string $singular,
567 2
        string $plural
568 2
    ): TemplateOutput {
569
        $folder = $settingsProvider
570 2
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_CONTROLLERS_FOLDER];
571 2
572
        $outputRootFolder = $this->filterOutFolderMask($folder);
573
        $outputFileName   = $plural . 'Controller.php';
574
        $outputContent    = $this->composeTemplateContent(
575 2
            $fileSystem,
576
            $this->getTemplatePath('JsonController.txt'),
577
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...LURAL_CC%}' => $plural) is of type array<string,string,{"{%...PLURAL_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
578
                '{%SINGULAR_CC%}' => $singular,
579
                '{%PLURAL_CC%}'   => $plural,
580
            ]
581
        );
582
583
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
584
    }
585
586 2
    /**
587
     * @param SettingsProviderInterface $settingsProvider
588
     * @param FileSystemInterface       $fileSystem
589
     * @param string                    $singular
590
     * @param string                    $plural
591
     *
592 2
     * @return TemplateOutput
593
     */
594 2
    private function composeJsonRoute(
595 2
        SettingsProviderInterface $settingsProvider,
596 2
        FileSystemInterface $fileSystem,
597 2
        string $singular,
598 2
        string $plural
599
    ): TemplateOutput {
600 2
        $folder = $settingsProvider->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_ROUTES_FOLDER];
601 2
602
        $outputRootFolder = $this->filterOutFolderMask($folder);
603
        $outputFileName   = $singular . 'ApiRoutes.php';
604
        $outputContent    = $this->composeTemplateContent(
605 2
            $fileSystem,
606
            $this->getTemplatePath('JsonRoutes.txt'),
607
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...LURAL_CC%}' => $plural) is of type array<string,string,{"{%...PLURAL_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
608
                '{%SINGULAR_CC%}' => $singular,
609
                '{%PLURAL_CC%}'   => $plural,
610
            ]
611
        );
612
613
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
614
    }
615 2
616
    /**
617
     * @param SettingsProviderInterface $settingsProvider
618
     * @param FileSystemInterface       $fileSystem
619
     * @param string                    $singular
620
     *
621 2
     * @return TemplateOutput
622
     */
623 2
    private function composeWebValidationOnCreateRules(
624 2
        SettingsProviderInterface $settingsProvider,
625 2
        FileSystemInterface $fileSystem,
626 2
        string $singular
627 2
    ): TemplateOutput {
628
        $folder = $settingsProvider
629 2
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_VALIDATORS_FOLDER];
630
631
        $outputRootFolder = $this->filterOutFolderMask($folder);
632
        $outputFileName   = $singular . 'CreateForm.php';
633 2
        $outputContent    = $this->composeTemplateContent(
634
            $fileSystem,
635
            $this->getTemplatePath('WebRulesOnCreate.txt'),
636
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' => $singular) is of type array<string,string,{"{%SINGULAR_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
637
                '{%SINGULAR_CC%}' => $singular,
638
            ]
639
        );
640
641
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent, $singular);
642
    }
643 2
644
    /**
645
     * @param SettingsProviderInterface $settingsProvider
646
     * @param FileSystemInterface       $fileSystem
647
     * @param string                    $singular
648
     *
649 2
     * @return TemplateOutput
650
     */
651 2
    private function composeWebValidationOnUpdateRules(
652 2
        SettingsProviderInterface $settingsProvider,
653 2
        FileSystemInterface $fileSystem,
654 2
        string $singular
655 2
    ): TemplateOutput {
656
        $folder = $settingsProvider
657 2
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_JSON_VALIDATORS_FOLDER];
658
659
        $outputRootFolder = $this->filterOutFolderMask($folder);
660
        $outputFileName   = $singular . 'UpdateForm.php';
661 2
        $outputContent    = $this->composeTemplateContent(
662
            $fileSystem,
663
            $this->getTemplatePath('WebRulesOnUpdate.txt'),
664
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' => $singular) is of type array<string,string,{"{%SINGULAR_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
665
                '{%SINGULAR_CC%}' => $singular,
666
            ]
667
        );
668
669
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent, $singular);
670
    }
671
672 2
    /**
673
     * @param SettingsProviderInterface $settingsProvider
674
     * @param FileSystemInterface       $fileSystem
675
     * @param string                    $singular
676
     * @param string                    $plural
677
     *
678
     * @return TemplateOutput
679 2
     */
680
    private function composeWebController(
681 2
        SettingsProviderInterface $settingsProvider,
682 2
        FileSystemInterface $fileSystem,
683 2
        string $singular,
684 2
        string $plural
685 2
    ): TemplateOutput {
686
        $folder = $settingsProvider
687 2
                      ->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_WEB_CONTROLLERS_FOLDER];
688 2
689 2
        $outputRootFolder = $this->filterOutFolderMask($folder);
690 2
        $outputFileName   = $plural . 'Controller.php';
691
        $outputContent    = $this->composeTemplateContent(
692
            $fileSystem,
693
            $this->getTemplatePath('WebController.txt'),
694 2
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...> \strtoupper($plural)) is of type array<string,string,{"{%...PLURAL_UC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
695
                '{%SINGULAR_CC%}' => $singular,
696
                '{%PLURAL_CC%}'   => $plural,
697
                '{%PLURAL_LC%}'   => strtolower($plural),
698
                '{%PLURAL_UC%}'   => strtoupper($plural),
699
            ]
700
        );
701
702
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
703
    }
704
705 2
    /**
706
     * @param SettingsProviderInterface $settingsProvider
707
     * @param FileSystemInterface       $fileSystem
708
     * @param string                    $singular
709
     * @param string                    $plural
710
     *
711 2
     * @return TemplateOutput
712
     */
713 2
    private function composeWebRoute(
714 2
        SettingsProviderInterface $settingsProvider,
715 2
        FileSystemInterface $fileSystem,
716 2
        string $singular,
717 2
        string $plural
718
    ): TemplateOutput {
719 2
        $folder = $settingsProvider->get(FluteSettingsInterface::class)[FluteSettingsInterface::KEY_ROUTES_FOLDER];
720 2
721
        $outputRootFolder = $this->filterOutFolderMask($folder);
722
        $outputFileName   = $singular . 'WebRoutes.php';
723
        $outputContent    = $this->composeTemplateContent(
724 2
            $fileSystem,
725
            $this->getTemplatePath('WebRoutes.txt'),
726
            [
0 ignored issues
show
Documentation introduced by
array('{%SINGULAR_CC%}' ...LURAL_CC%}' => $plural) is of type array<string,string,{"{%...PLURAL_CC%}":"string"}>, but the function expects a object<Limoncello\Application\Commands\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
727
                '{%SINGULAR_CC%}' => $singular,
728
                '{%PLURAL_CC%}'   => $plural,
729
            ]
730
        );
731
732
        return new TemplateOutput($outputRootFolder, $outputFileName, $outputContent);
733
    }
734
735 8
    /**
736
     * @param FileSystemInterface $fileSystem
737
     * @param TemplateOutput[]    $templateOutputs
738 8
     *
739 8
     * @return void
740 1
     *
741 1
     * @SuppressWarnings(PHPMD.ElseExpression)
742
     */
743
    private function createTemplates(FileSystemInterface $fileSystem, array $templateOutputs): void
744 8
    {
745 1
        // before making any changes in the filesystem we have to check there is a good chance we can make it
746 1
        foreach ($templateOutputs as $templateOutput) {
747
            if ($fileSystem->exists($templateOutput->getOutputRootFolder()) === false) {
748
                $rootFolder = $templateOutput->getOutputRootFolder();
749 8
                throw new InvalidArgumentException("Folder `$rootFolder` do not exist.");
750 8
            }
751
752 7
            if ($fileSystem->exists($templateOutput->getOutputPath()) === true) {
753 7
                $filePath = $templateOutput->getOutputPath();
754
                throw new InvalidArgumentException("File `$filePath` already exists.");
755
            }
756
757 4
            $outFolder = $templateOutput->getOutputFolder();
758 4
            if ($fileSystem->exists($outFolder) === true) {
759 8
                // the folder already exist so we have to check it is writable
760
                if ($fileSystem->isWritable($outFolder) === false) {
761
                    throw new InvalidArgumentException("Folder `$outFolder` is not writable.");
762
                }
763
            } else {
764 4
                // it should be a root folder with not yet existing sub-folder so root should be writable
765 4
                $rootFolder = $templateOutput->getOutputRootFolder();
766 3
                if ($fileSystem->isWritable($rootFolder) === false) {
767
                    throw new InvalidArgumentException("Folder `$rootFolder` is not writable.");
768 4
                }
769
            }
770
        }
771
772
        foreach ($templateOutputs as $templateOutput) {
773
            if ($fileSystem->exists($templateOutput->getOutputFolder()) === false) {
774
                $fileSystem->createFolder($templateOutput->getOutputFolder());
775
            }
776
            $fileSystem->write($templateOutput->getOutputPath(), $templateOutput->getOutputContent());
777
        }
778
    }
779 8
780
    /**
781
     * @param FileSystemInterface $fileSystem
782
     * @param string              $templatePath
783
     * @param iterable            $templateParams
784 8
     *
785
     * @return string
786 8
     */
787 8
    private function composeTemplateContent(
788
        FileSystemInterface $fileSystem,
789
        string $templatePath,
790 8
        iterable $templateParams
791
    ): string {
792
        $templateContent = $fileSystem->read($templatePath);
793
794
        foreach ($templateParams as $key => $value) {
795
            $templateContent = str_replace($key, $value, $templateContent);
796
        }
797
798 11
        return $templateContent;
799
    }
800 11
801
    /**
802
     * @param string $name
803
     *
804
     * @return bool
805
     */
806
    private function isValidShortClassName(string $name): bool
807
    {
808 8
        return empty($name) === false && preg_match(static::VALID_CLASS_NAME_REGEX, $name) === 1;
809
    }
810 8
811
    /**
812
     * @param string $fileName
813
     *
814
     * @return string
815
     */
816
    private function getTemplatePath(string $fileName): string
817
    {
818
        return implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', 'res', 'CodeTemplates', $fileName]);
819
    }
820 8
821
    /**
822 8
     * Folder paths might include masks such as `**`. This function tries to filter them out.
823
     *
824 8
     * @param string $folder
825 8
     *
826
     * @return string
827 8
     */
828
    private function filterOutFolderMask(string $folder): string
829
    {
830
        $mask = '**';
831
832
        $folder = str_replace($mask . DIRECTORY_SEPARATOR, '', $folder);
833
        $folder = str_replace($mask, '', $folder);
834
835
        return $folder;
836
    }
837
}
838