Passed
Push — develop ( 2dd196...7610c9 )
by nguereza
02:59
created

MakeCommand::replaceMethodBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
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, mixed>>
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
        $replaceMethodBody = $this->replaceMethodBody($replaceConstructor);
281
282
        return $replaceMethodBody;
283
    }
284
285
    /**
286
     * Return the short class name
287
     * @return string
288
     */
289
    protected function getShortClassName(): string
290
    {
291
        $namespace = $this->getNamespace();
292
293
        return Str::replaceFirst(
294
            $namespace . '\\',
295
            '',
296
            $this->getFullClassName($this->className)
297
        );
298
    }
299
300
    /**
301
     * Create the class parent(s) directory if it does not exist
302
     * @param string $path
303
     * @return void
304
     */
305
    protected function createParentDirectory(string $path): void
306
    {
307
        $file = $this->filesystem->file($path);
308
        $location = $file->getLocation();
309
        if (!empty($location)) {
310
            $directory = $this->filesystem->directory($location);
311
            if (!$directory->exists()) {
312
                $directory->create('', 0775, true);
313
            }
314
        }
315
    }
316
317
    /**
318
     * Replace the name space
319
     * @param string $content
320
     * @return string
321
     */
322
    protected function replaceNamespace(string $content): string
323
    {
324
        $namespace = $this->getNamespace();
325
        return str_replace('%namespace%', $namespace, $content);
326
    }
327
328
    /**
329
     * Replace the properties
330
     * @param string $content
331
     * @return string
332
     */
333
    protected function replaceProperties(string $content): string
334
    {
335
        $replaceContent = $this->getPropertiesContent();
336
        return str_replace('%properties%', $replaceContent, $content);
337
    }
338
339
    /**
340
     * Replace the constructor
341
     * @param string $content
342
     * @return string
343
     */
344
    protected function replaceConstructor(string $content): string
345
    {
346
        $replaceContent = $this->getConstructorContent();
347
348
        return str_replace('%constructor%', $replaceContent, $content);
349
    }
350
    
351
    /**
352
     * Replace the method body
353
     * @param string $content
354
     * @return string
355
     */
356
    protected function replaceMethodBody(string $content): string
357
    {
358
        $replaceContent = $this->getMethodBodyContent();
359
360
        return str_replace('%method_body%', $replaceContent, $content);
361
    }
362
363
    /**
364
     * Replace the class uses instructions
365
     * @param string $content
366
     * @return string
367
     */
368
    protected function replaceClassUses(string $content): string
369
    {
370
        $replaceContent = $this->getUsesContent();
371
        return str_replace('%uses%', $replaceContent, $content);
372
    }
373
374
    /**
375
     * Replace the classes
376
     * @param string $content
377
     * @return string
378
     */
379
    protected function replaceClasses(string $content): string
380
    {
381
        $shortClassName = $this->getShortClassName();
382
        $fullClassName = $this->getFullClassName($this->className);
383
384
        $replaced = str_replace('%classname%', $shortClassName, $content);
385
386
        return str_replace('%fullclassname%', $fullClassName, $replaced);
387
    }
388
389
    /**
390
     * Return the properties content
391
     * @return string
392
     */
393
    protected function getPropertiesContent(): string
394
    {
395
        if (empty($this->properties)) {
396
            return '';
397
        }
398
399
        $content = '';
400
401
        foreach ($this->properties as $className => $info) {
402
            $content .= $this->getPropertyTemplate($className, $info);
403
        }
404
405
        return $content;
406
    }
407
408
    /**
409
     * Return the name space uses content
410
     * @return string
411
     */
412
    protected function getUsesContent(): string
413
    {
414
        if (empty($this->properties)) {
415
            return '';
416
        }
417
418
        $content = '';
419
420
        foreach ($this->properties as $className => $info) {
421
            $content .= $this->getUsesTemplate($className);
422
        }
423
424
        return $content;
425
    }
426
427
428
    /**
429
     * Return the constructor content
430
     * @return string
431
     */
432
    protected function getConstructorContent(): string
433
    {
434
        if (empty($this->properties)) {
435
            return '';
436
        }
437
438
        $docblock = $this->getConstructorDocBlockContent();
439
        $params = $this->getConstructorParamsContent();
440
        $body = $this->getConstructorBodyContent();
441
442
        return <<<EOF
443
        $docblock
444
            public function __construct(
445
               $params
446
            ){
447
                $body
448
            }
449
        EOF;
450
    }
451
    
452
    /**
453
     * Return the constructor content
454
     * @return string
455
     */
456
    protected function getMethodBodyContent(): string
457
    {
458
        return '';
459
    }
460
461
462
    /**
463
     * Return the constructor document block comment content
464
     * @return string
465
     */
466
    protected function getConstructorDocBlockContent(): string
467
    {
468
        $content = '';
469
        foreach ($this->properties as $className => $info) {
470
            $content .= $this->getConstructorDocBlockTemplate($className, $info);
471
        }
472
473
        return <<<EOF
474
        /**
475
            * Create new instance
476
            $content*/
477
        EOF;
478
    }
479
480
    /**
481
     * Return the constructor parameters content
482
     * @return string
483
     */
484
    protected function getConstructorParamsContent(): string
485
    {
486
        $content = '';
487
        $i = 1;
488
        $count = count($this->properties);
489
        foreach ($this->properties as $className => $info) {
490
            $content .= $this->getConstructorParamsTemplate($className, $info, $i === $count);
491
            $i++;
492
        }
493
494
        return $content;
495
    }
496
497
    /**
498
     * Return the constructor body content
499
     * @return string
500
     */
501
    protected function getConstructorBodyContent(): string
502
    {
503
        $content = '';
504
        $i = 1;
505
        $count = count($this->properties);
506
        foreach ($this->properties as $className => $info) {
507
            $content .= $this->getConstructorBodyTemplate($className, $info, $i === $count);
508
            $i++;
509
        }
510
511
        return $content;
512
    }
513
514
    /**
515
     * Return the constructor document block template for the given class
516
     * @param string $className
517
     * @param array<string, string> $info
518
     * @return string
519
     */
520
    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

520
    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...
521
    {
522
        $shortClass = $info['short'];
523
        $name = $info['name'];
524
525
        return <<<EOF
526
        * @param $shortClass \$$name 
527
            
528
        EOF;
529
    }
530
531
    /**
532
     * Return the constructor arguments template for the given class
533
     * @param string $className
534
     * @param array<string, string> $info
535
     * @param bool $isLast
536
     * @return string
537
     */
538
    protected function getConstructorParamsTemplate(
539
        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

539
        /** @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...
540
        array $info,
541
        bool $isLast = false
542
    ): string {
543
        $shortClass = $info['short'];
544
        $name = $info['name'];
545
        $comma = $isLast ? '' : ',';
546
547
        if ($isLast) {
548
            return <<<EOF
549
            $shortClass \$$name$comma
550
            EOF;
551
        }
552
553
        return <<<EOF
554
        $shortClass \$$name$comma
555
               
556
        EOF;
557
    }
558
559
    /**
560
     * Return the constructor body template for the given class
561
     * @param string $className
562
     * @param array<string, string> $info
563
     * @param bool $isLast
564
     * @return string
565
     */
566
    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

566
    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...
567
    {
568
        $name = $info['name'];
569
570
        if ($isLast) {
571
            return <<<EOF
572
            \$this->$name = \$$name;
573
            EOF;
574
        }
575
576
        return <<<EOF
577
        \$this->$name = \$$name;
578
                
579
        EOF;
580
    }
581
582
    /**
583
     * Return the property template for the given class
584
     * @param string $className
585
     * @param array<string, mixed> $info
586
     * @return string
587
     */
588
    protected function getPropertyTemplate(string $className, array $info): string
589
    {
590
        $shortClass = $info['short'];
591
        $name = $info['name'];
592
593
        return <<<EOF
594
        /**
595
            * The $shortClass instance
596
            * @var $shortClass
597
            */
598
            protected $shortClass \$$name;
599
        
600
            
601
        EOF;
602
    }
603
604
    /**
605
     * Return the name space use template for the given class
606
     * @param string $className
607
     * @return string
608
     */
609
    protected function getUsesTemplate(string $className): string
610
    {
611
        return <<<EOF
612
        use $className; 
613
        
614
        EOF;
615
    }
616
}
617