Passed
Push — develop ( 471dff...67d5d0 )
by nguereza
05:03
created

MakeCommand   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 529
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 182
c 1
b 0
f 0
dl 0
loc 529
rs 8.72
wmc 46

27 Methods

Rating   Name   Duplication   Size   Complexity  
A interact() 0 10 2
A __construct() 0 10 1
A execute() 0 42 4
A fileExists() 0 5 1
A replaceClassUses() 0 4 1
A getConstructorParamsContent() 0 11 2
A getPropertiesContent() 0 13 3
A replaceConstructor() 0 5 1
A getConstructorDocBlockContent() 0 11 2
A replaceClasses() 0 8 1
A getUsesContent() 0 13 3
A getPropertyTemplate() 0 11 1
A getConstructorParamsTemplate() 0 17 3
A getShortClassName() 0 8 1
A getConstructorDocBlockTemplate() 0 7 1
A createClass() 0 11 1
A getConstructorBodyTemplate() 0 12 2
A getConstructorBodyContent() 0 11 2
A replaceNamespace() 0 4 1
A createParentDirectory() 0 8 3
A getClassBaseName() 0 9 2
A getPath() 0 11 1
A getUsesTemplate() 0 4 1
A getConstructorContent() 0 16 2
A getNamespace() 0 8 1
A getFullClassName() 0 16 2
A replaceProperties() 0 4 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.

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
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file MakeCommand.php
34
 *
35
 *  The Make Command base class
36
 *
37
 *  @package    Platine\Framework\Console
38
 *  @author Platine Developers team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   https://www.platine-php.com
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Framework\Console;
49
50
use Platine\Console\Command\Command;
51
use Platine\Console\Input\Reader;
52
use Platine\Console\Output\Writer;
53
use Platine\Filesystem\Filesystem;
54
use Platine\Framework\App\Application;
55
use Platine\Stdlib\Helper\Path;
56
use Platine\Stdlib\Helper\Str;
57
58
/**
59
 * @class MakeCommand
60
 * @package Platine\Framework\Console
61
 */
62
abstract class MakeCommand extends Command
63
{
64
    /**
65
     * The Application instance
66
     * @var Application
67
     */
68
    protected Application $application;
69
70
    /**
71
     * The file system to use
72
     * @var Filesystem
73
     */
74
    protected Filesystem $filesystem;
75
76
    /**
77
     * The application root name space
78
     * @var string
79
     */
80
    protected string $rootNamespace;
81
82
    /**
83
     * The type of class
84
     * @var string
85
     */
86
    protected string $type = '';
87
88
    /**
89
     * The class full name given by user
90
     * @var string
91
     */
92
    protected string $className = '';
93
94
    /**
95
     * The action properties
96
     * @var array<string, array<string, string>>
97
     */
98
    protected array $properties = [];
99
100
    /**
101
     * Create new instance
102
     * @param Application $application
103
     * @param Filesystem $filesystem
104
     */
105
    public function __construct(
106
        Application $application,
107
        Filesystem $filesystem
108
    ) {
109
        parent::__construct('make', 'Command to generate class skeleton');
110
        $this->application = $application;
111
        $this->filesystem = $filesystem;
112
        $this->rootNamespace = $application->getNamespace();
113
        $this->addArgument('name', 'The full class name (can include root namespace', null, false);
114
        $this->addOption('-f|--force', 'Overwrite existing files.', false, false);
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function execute()
121
    {
122
        $io = $this->io();
123
        $writer = $io->writer();
124
        $name = $this->className;
125
126
        $className = $this->getFullClassName($name);
127
        $path = $this->getPath();
128
        $namespace = $this->getNamespace();
129
130
        $writer->boldGreen(sprintf(
131
            'Generation of new %s class [%s]',
132
            $this->type,
133
            $className
134
        ), true)->eol();
135
136
137
        if ($this->fileExists() && !$this->getOptionValue('force')) {
138
            $writer->red(sprintf(
139
                'File [%s] already exists.',
140
                $path
141
            ), true);
142
143
            return;
144
        }
145
146
        $writer->bold('Class: ');
147
        $writer->boldBlueBgBlack($className, true);
148
149
        $writer->bold('Path: ');
150
        $writer->boldBlueBgBlack($path, true);
151
152
        $writer->bold('Namespace: ');
153
        $writer->boldBlueBgBlack($namespace, true);
154
155
        if ($io->confirm(sprintf('Are you confirm the generation of [%s] ?', $className), 'y')) {
156
            $this->createParentDirectory($path);
157
            $content = $this->createClass();
158
159
            $file = $this->filesystem->file($path);
160
            $file->write($content);
161
            $writer->boldGreen(sprintf('Class [%s] generated successfully.', $className), true);
162
        }
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function interact(Reader $reader, Writer $writer): void
169
    {
170
        $writer->boldYellow('GENERATION OF NEW CLASS', true)->eol();
171
        $name = $this->getArgumentValue('name');
172
        if (empty($name)) {
173
            $io = $this->io();
174
            $name = $io->prompt('Enter the full class name (can include root namespace)', null);
175
        }
176
177
        $this->className = $name;
178
    }
179
    
180
    /**
181
     * Return the base class name
182
     * @param string|object $fullClassName
183
     * @return string
184
     */
185
    public function getClassBaseName($fullClassName): string
186
    {
187
        if (is_object($fullClassName)) {
188
            $fullClassName = get_class($fullClassName);
189
        }
190
191
        $temp = explode('\\', $fullClassName);
192
        
193
        return end($temp);
194
    }
195
196
    /**
197
     * Return the the class template
198
     * @return string
199
     */
200
    abstract public function getClassTemplate(): string;
201
202
    /**
203
     * Return the real path for the given name
204
     * @return string
205
     */
206
    protected function getPath(): string
207
    {
208
        $class = Str::replaceFirst($this->rootNamespace, '', $this->className);
209
210
        $path = sprintf(
211
            '%s/%s.php',
212
            $this->application->getAppPath(),
213
            str_replace('\\', '/', $class)
214
        );
215
216
        return Path::normalizePathDS($path);
217
    }
218
219
    /**
220
     * Return the class name with the root name space
221
     * @param string $name
222
     * @return string
223
     */
224
    protected function getFullClassName(string $name): string
225
    {
226
        $classClean = ltrim($name, '/\\');
227
        $class = str_replace('/', '\\', $classClean);
228
229
        if (Str::startsWith($this->rootNamespace, $class)) {
230
            return $class;
231
        }
232
233
        $fullyClass = $this->getFullClassName(sprintf(
234
            '%s\\%s',
235
            trim($this->rootNamespace, '\\'),
236
            $class
237
        ));
238
239
        return $fullyClass;
240
    }
241
242
    /**
243
     * Return the full name space for the given class
244
     * @return string
245
     */
246
    protected function getNamespace(): string
247
    {
248
        $class = str_replace('/', '\\', $this->className);
249
250
        return $this->rootNamespace . trim(implode(
251
            '\\',
252
            array_slice(explode('\\', $class), 0, -1)
253
        ), '\\');
254
    }
255
256
    /**
257
     * Whether the file for the given name already exists
258
     * @return bool
259
     */
260
    protected function fileExists(): bool
261
    {
262
        $path = $this->getPath();
263
264
        return $this->filesystem->file($path)->exists();
265
    }
266
267
    /**
268
     * Create the class for the given name
269
     * @return string
270
     */
271
    protected function createClass(): string
272
    {
273
        $template = $this->getClassTemplate();
274
275
        $replaceNamespace = $this->replaceNamespace($template);
276
        $replaceUses = $this->replaceClassUses($replaceNamespace);
277
        $replaceClasses = $this->replaceClasses($replaceUses);
278
        $replaceProperties = $this->replaceProperties($replaceClasses);
279
        $replaceConstructor = $this->replaceConstructor($replaceProperties);
280
281
        return $replaceConstructor;
282
    }
283
284
    /**
285
     * Return the short class name
286
     * @return string
287
     */
288
    protected function getShortClassName(): string
289
    {
290
        $namespace = $this->getNamespace();
291
292
        return Str::replaceFirst(
293
            $namespace . '\\',
294
            '',
295
            $this->getFullClassName($this->className)
296
        );
297
    }
298
299
    /**
300
     * Create the class parent(s) directory if it does not exist
301
     * @param string $path
302
     * @return void
303
     */
304
    protected function createParentDirectory(string $path): void
305
    {
306
        $file = $this->filesystem->file($path);
307
        $location = $file->getLocation();
308
        if (!empty($location)) {
309
            $directory = $this->filesystem->directory($location);
310
            if (!$directory->exists()) {
311
                $directory->create('', 0775, true);
312
            }
313
        }
314
    }
315
316
    /**
317
     * Replace the name space
318
     * @param string $content
319
     * @return string
320
     */
321
    protected function replaceNamespace(string $content): string
322
    {
323
        $namespace = $this->getNamespace();
324
        return str_replace('%namespace%', $namespace, $content);
325
    }
326
327
    /**
328
     * Replace the properties
329
     * @param string $content
330
     * @return string
331
     */
332
    protected function replaceProperties(string $content): string
333
    {
334
        $replaceContent = $this->getPropertiesContent();
335
        return str_replace('%properties%', $replaceContent, $content);
336
    }
337
338
    /**
339
     * Replace the constructor
340
     * @param string $content
341
     * @return string
342
     */
343
    protected function replaceConstructor(string $content): string
344
    {
345
        $replaceContent = $this->getConstructorContent();
346
347
        return str_replace('%constructor%', $replaceContent, $content);
348
    }
349
350
    /**
351
     * Replace the class uses instructions
352
     * @param string $content
353
     * @return string
354
     */
355
    protected function replaceClassUses(string $content): string
356
    {
357
        $replaceContent = $this->getUsesContent();
358
        return str_replace('%uses%', $replaceContent, $content);
359
    }
360
361
    /**
362
     * Replace the classes
363
     * @param string $content
364
     * @return string
365
     */
366
    protected function replaceClasses(string $content): string
367
    {
368
        $shortClassName = $this->getShortClassName();
369
        $fullClassName = $this->getFullClassName($this->className);
370
371
        $replaced = str_replace('%classname%', $shortClassName, $content);
372
373
        return str_replace('%fullclassname%', $fullClassName, $replaced);
374
    }
375
376
    /**
377
     * Return the properties content
378
     * @return string
379
     */
380
    protected function getPropertiesContent(): string
381
    {
382
        if (empty($this->properties)) {
383
            return '';
384
        }
385
386
        $content = '';
387
388
        foreach ($this->properties as $className => $info) {
389
            $content .= $this->getPropertyTemplate($className, $info);
390
        }
391
392
        return $content;
393
    }
394
395
    /**
396
     * Return the name space uses content
397
     * @return string
398
     */
399
    protected function getUsesContent(): string
400
    {
401
        if (empty($this->properties)) {
402
            return '';
403
        }
404
405
        $content = '';
406
407
        foreach ($this->properties as $className => $info) {
408
            $content .= $this->getUsesTemplate($className);
409
        }
410
411
        return $content;
412
    }
413
414
415
    /**
416
     * Return the constructor content
417
     * @return string
418
     */
419
    protected function getConstructorContent(): string
420
    {
421
        if (empty($this->properties)) {
422
            return '';
423
        }
424
425
        $docblock = $this->getConstructorDocBlockContent();
426
        $params = $this->getConstructorParamsContent();
427
        $body = $this->getConstructorBodyContent();
428
429
        return <<<EOF
430
        $docblock
431
            public function __construct(
432
               $params
433
            ){
434
                $body
435
            }
436
        EOF;
437
    }
438
439
440
    /**
441
     * Return the constructor document block comment content
442
     * @return string
443
     */
444
    protected function getConstructorDocBlockContent(): string
445
    {
446
        $content = '';
447
        foreach ($this->properties as $className => $info) {
448
            $content .= $this->getConstructorDocBlockTemplate($className, $info);
449
        }
450
451
        return <<<EOF
452
        /**
453
            * Create new instance
454
            $content*/
455
        EOF;
456
    }
457
458
    /**
459
     * Return the constructor parameters content
460
     * @return string
461
     */
462
    protected function getConstructorParamsContent(): string
463
    {
464
        $content = '';
465
        $i = 1;
466
        $count = count($this->properties);
467
        foreach ($this->properties as $className => $info) {
468
            $content .= $this->getConstructorParamsTemplate($className, $info, $i === $count);
469
            $i++;
470
        }
471
472
        return $content;
473
    }
474
475
    /**
476
     * Return the constructor body content
477
     * @return string
478
     */
479
    protected function getConstructorBodyContent(): string
480
    {
481
        $content = '';
482
        $i = 1;
483
        $count = count($this->properties);
484
        foreach ($this->properties as $className => $info) {
485
            $content .= $this->getConstructorBodyTemplate($className, $info, $i === $count);
486
            $i++;
487
        }
488
489
        return $content;
490
    }
491
492
    /**
493
     * Return the constructor document block template for the given class
494
     * @param string $className
495
     * @param array<string, string> $info
496
     * @return string
497
     */
498
    protected function getConstructorDocBlockTemplate(string $className, array $info): string
0 ignored issues
show
Unused Code introduced by
The parameter $className is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

498
    protected function getConstructorDocBlockTemplate(/** @scrutinizer ignore-unused */ string $className, array $info): string

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

Loading history...
499
    {
500
        $shortClass = $info['short'];
501
        $name = $info['name'];
502
503
        return <<<EOF
504
        * @param $shortClass \$$name 
505
            
506
        EOF;
507
    }
508
509
    /**
510
     * Return the constructor arguments template for the given class
511
     * @param string $className
512
     * @param array<string, string> $info
513
     * @param bool $isLast
514
     * @return string
515
     */
516
    protected function getConstructorParamsTemplate(
517
        string $className,
0 ignored issues
show
Unused Code introduced by
The parameter $className is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

517
        /** @scrutinizer ignore-unused */ string $className,

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

Loading history...
518
        array $info,
519
        bool $isLast = false
520
    ): string {
521
        $shortClass = $info['short'];
522
        $name = $info['name'];
523
        $comma = $isLast ? '' : ',';
524
525
        if ($isLast) {
526
            return <<<EOF
527
            $shortClass \$$name$comma
528
            EOF;
529
        }
530
531
        return <<<EOF
532
        $shortClass \$$name$comma
533
               
534
        EOF;
535
    }
536
537
    /**
538
     * Return the constructor body template for the given class
539
     * @param string $className
540
     * @param array<string, string> $info
541
     * @param bool $isLast
542
     * @return string
543
     */
544
    protected function getConstructorBodyTemplate(string $className, array $info, bool $isLast = false): string
0 ignored issues
show
Unused Code introduced by
The parameter $className is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

544
    protected function getConstructorBodyTemplate(/** @scrutinizer ignore-unused */ string $className, array $info, bool $isLast = false): string

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

Loading history...
545
    {
546
        $name = $info['name'];
547
548
        if ($isLast) {
549
            return <<<EOF
550
            \$this->$name = \$$name;
551
            EOF;
552
        }
553
554
        return <<<EOF
555
        \$this->$name = \$$name;
556
                
557
        EOF;
558
    }
559
560
    /**
561
     * Return the property template for the given class
562
     * @param string $className
563
     * @param array<string, string> $info
564
     * @return string
565
     */
566
    protected function getPropertyTemplate(string $className, array $info): string
567
    {
568
        $shortClass = $info['short'];
569
        $name = $info['name'];
570
571
        return <<<EOF
572
        /**
573
            * The $shortClass instance
574
            * @var $shortClass
575
            */
576
            protected $shortClass \$$name;
577
        
578
            
579
        EOF;
580
    }
581
582
    /**
583
     * Return the name space use template for the given class
584
     * @param string $className
585
     * @return string
586
     */
587
    protected function getUsesTemplate(string $className): string
588
    {
589
        return <<<EOF
590
        use $className; 
591
        
592
        EOF;
593
    }
594
}
595