Test Failed
Pull Request — main (#17)
by Dimitri
16:16 queued 22s
created

GeneratorTrait::normalizeInputClassName()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 34
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 9.2978

Importance

Changes 0
Metric Value
cc 8
eloc 14
c 0
b 0
f 0
nc 8
nop 0
dl 0
loc 34
ccs 8
cts 11
cp 0.7272
crap 9.2978
rs 8.4444
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Cli\Traits;
13
14
use BlitzPHP\View\Adapters\NativeAdapter;
15
16
/**
17
 * GeneratorTrait contient une collection de méthodes
18
 * pour construire les commandes qui génèrent un fichier.
19
 */
20
trait GeneratorTrait
21
{
22
    /**
23
     * Nom du composant
24
     *
25
     * @var string
26
     */
27
    protected $component;
28
29
    /**
30
     * Répertoire de fichiers
31
     *
32
     * @var string
33
     */
34
    protected $directory;
35
36
    /**
37
     * Nom de la vue du template
38
     *
39
     * @var string
40
     */
41
    protected $template;
42
43
    /**
44
     * Chemin dans du dossier dans lequelle les vues de generation sont cherchees
45
     *
46
     * @var string
47
     */
48
    protected $templatePath = SYST_PATH . 'Cli/Commands/Generators/Views';
49
50
    /**
51
     * Clé de chaîne de langue pour les noms de classe requis.
52
     *
53
     * @var string
54
     */
55
    protected $classNameLang = '';
56
57
    /**
58
     * Namespace a utiliser pour la classe.
59
     * Laisser a null pour utiliser le namespace par defaut.
60
     */
61
    protected ?string $namespace = null;
62
63
    /**
64
     * S'il faut exiger le nom de la classe.
65
     *
66
     * @internal
67
     *
68
     * @var bool
69
     */
70
    private $hasClassName = true;
71
72
    /**
73
     * S'il faut trier les importations de classe.
74
     *
75
     * @internal
76
     *
77
     * @var bool
78
     */
79
    private $sortImports = true;
80
81
    /**
82
     * Indique si l'option `--suffix` a un effet.
83
     *
84
     * @internal
85
     *
86
     * @var bool
87
     */
88
    private $enabledSuffixing = true;
89
90
    /**
91
     * Le tableau params pour un accès facile par d'autres méthodes.
92
     *
93
     * @internal
94
     *
95
     * @var array<int|string, string|null>
96
     */
97
    private $params = [];
98
99
    /**
100
     * Exécute la generation.
101
	 *
102
     * @param array<int|string, string|null> $params
103
	 *
104
	 * @deprecated use generateClass() instead
105
     */
106
    protected function runGeneration(array $params): void
107
    {
108
        $this->generateClass($params);
109
    }
110
111
    /**
112
     * Génère un fichier de classe à partir d'un template existant.
113
     *
114
     * @param array<int|string, string|null> $params
115
     *
116
     * @return string|null Nom de la classe
117
     */
118
    protected function generateClass(array $params): ?string
119
    {
120 2
        $this->params = $params;
121
122
        // Récupère le nom complet de la classe à partir de l'entrée.
123 2
        $class = $this->qualifyClassName();
124
125
        // Obtenir le chemin d'accès au fichier à partir du nom de la classe.
126 2
        $target = $this->buildPath($class);
127
128
        // Vérifier si le chemin est vide.
129
        if ($target === '') {
130
            return null;
131
        }
132
133 2
        $this->generateFile($target, $this->buildContent($class));
134
135 2
        return $class;
136
    }
137
138
    /**
139
     * Générer un fichier de vue à partir d'un template existant.
140
     *
141
     * @param string                         $view   nom de la vue à espace de noms qui est générée
142
     * @param array<int|string, string|null> $params
143
     */
144
    protected function generateView(string $view, array $params): void
145
    {
146 2
        $this->params = $params;
147
148 2
        $target = $this->buildPath($view);
149
150
        // Vérifier si le chemin est vide.
151
        if ($target === '') {
152
            return;
153
        }
154
155 2
        $this->generateFile($target, $this->buildContent($view));
156
    }
157
158
    /**
159
     * Handles writing the file to disk, and all of the safety checks around that.
160
     */
161
    private function generateFile(string $target, string $content): void
162
    {
163
        if ($this->option('namespace') === 'BlitzPHP') {
0 ignored issues
show
Bug introduced by
It seems like option() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

163
        if ($this->/** @scrutinizer ignore-call */ option('namespace') === 'BlitzPHP') {
Loading history...
164
            // @codeCoverageIgnoreStart
165
            $this->colorize(lang('CLI.generator.usingBlitzNamespace'), 'yellow');
0 ignored issues
show
Bug introduced by
It seems like colorize() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

165
            $this->/** @scrutinizer ignore-call */ 
166
                   colorize(lang('CLI.generator.usingBlitzNamespace'), 'yellow');
Loading history...
166
167
            if (! $this->confirm('Are you sure you want to continue?')) {
0 ignored issues
show
Bug introduced by
It seems like confirm() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

167
            if (! $this->/** @scrutinizer ignore-call */ confirm('Are you sure you want to continue?')) {
Loading history...
168
                $this->eol()->colorize(lang('CLI.generator.cancelOperation'), 'yellow');
0 ignored issues
show
Bug introduced by
It seems like eol() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

168
                $this->/** @scrutinizer ignore-call */ 
169
                       eol()->colorize(lang('CLI.generator.cancelOperation'), 'yellow');
Loading history...
169
170
                return;
171
            }
172
173
            $this->eol();
174
            // @codeCoverageIgnoreEnd
175
        }
176
177 2
        $isFile = is_file($target);
178
179
        // Écraser des fichiers sans le savoir est une gêne sérieuse, nous allons donc vérifier si nous dupliquons des choses,
180
        // si l'option "forcer" n'est pas fournie, nous renvoyons.
181
        if (! $this->option('force') && $isFile) {
182 2
            $this->io->error(lang('CLI.generator.fileExist', [clean_path($target)]), true);
183
184
            return;
185
        }
186
187
        // Vérifie si le répertoire pour enregistrer le fichier existe.
188 2
        $dir = dirname($target);
189
190
        if (! is_dir($dir)) {
191 2
            mkdir($dir, 0o755, true);
192
        }
193
194 2
        helper('filesystem');
195
196
        // Construisez la classe en fonction des détails dont nous disposons.
197
        // Nous obtiendrons le contenu de notre fichier à partir du modèle,
198
        // puis nous effectuerons les remplacements nécessaires.
199
        if (! write_file($target, $content)) {
200
            // @codeCoverageIgnoreStart
201
            $this->io->error(lang('CLI.generator.fileError', [clean_path($target)]), true);
202
203
            return;
204
            // @codeCoverageIgnoreEnd
205
        }
206
207
        if ($this->option('force') && $isFile) {
208 2
            $this->colorize(lang('CLI.generator.fileOverwrite', [clean_path($target)]), 'yellow');
209
210
            return;
211
        }
212
213 2
        $this->colorize(lang('CLI.generator.fileCreate', [clean_path($target)]), 'green');
214
    }
215
216
    /**
217
     * Préparez les options et effectuez les remplacements nécessaires.
218
     *
219
     * @param string $class nom de classe avec namespace ou vue avec namespace.
220
     *
221
     * @return string contenu du fichier généré
222
     */
223
    protected function prepare(string $class): string
224
    {
225 2
        return $this->parseTemplate($class);
226
    }
227
228
    /**
229
     * Changez le nom de base du fichier avant de l'enregistrer.
230
     *
231
     * Utile pour les composants dont le nom de fichier comporte une date.
232
     */
233
    protected function basename(string $filename): string
234
    {
235 2
        return basename($filename);
236
    }
237
238
    /**
239
     * Formatte le nom de base du fichier avant de l'enregistrer.
240
     *
241
     * Utile pour les composants dont le nom de fichier doit etre dans un format particulier.
242
     */
243
    protected function formatFilename(string $namespace, string $class): string
244
    {
245 2
        return str_replace('\\', DS, trim(str_replace($namespace . '\\', '', $class), '\\'));
246
    }
247
248
    /**
249
     * Analyse le nom de la classe et vérifie s'il est déjà qualifié.
250
     */
251
    protected function qualifyClassName(): string
252
    {
253 2
        $class = $this->normalizeInputClassName();
254
255
        // Récupère l'espace de noms à partir de l'entrée. N'oubliez pas le backslash finale !
256 2
        $namespace = $this->getNamespace() . '\\';
257
258
        if (str_starts_with($class, $namespace)) {
259
            return $class; // @codeCoverageIgnore
260
        }
261
262 2
        $directory = ($this->directory !== null) ? $this->directory . '\\' : '';
263
264 2
        return $namespace . $directory . str_replace('/', '\\', $class);
265
    }
266
267
    /**
268
     * Obtient la vue du générateur
269
     */
270
    protected function renderTemplate(array $data = []): string
271
    {
272 2
        $viewer = new NativeAdapter([], $this->templatePath, false);
273
274 2
        return $viewer->setData($data)->render($this->template);
275
    }
276
277
    /**
278
     * Exécute les pseudo-variables contenues dans le fichier de vue.
279
	 *
280
	 * @param string $class nom de classe avec namespace ou vue avec namespace.
281
     */
282
    protected function parseTemplate(string $class, array $search = [], array $replace = [], array $data = []): string
283
    {
284
        // Récupère la partie de l'espace de noms à partir du nom de classe complet.
285 2
        $namespace = trim(implode('\\', array_slice(explode('\\', $class), 0, -1)), '\\');
286 2
        $search[]  = '<@php';
287 2
        $search[]  = '{namespace}';
288 2
        $search[]  = '{class}';
289 2
        $replace[] = '<?php';
290 2
        $replace[] = $namespace;
291 2
        $replace[] = str_replace($namespace . '\\', '', $class);
292
293 2
        return str_replace($search, $replace, $this->renderTemplate($data));
294
    }
295
296
    /**
297
     * Construit le contenu de la classe générée, effectue tous les remplacements nécessaires et
298
     * trie par ordre alphabétique les importations pour un modèle donné.
299
     */
300
    protected function buildContent(string $class): string
301
    {
302 2
        $template = $this->prepare($class);
303
304
        if ($this->sortImports && preg_match('/(?P<imports>(?:^use [^;]+;$\n?)+)/m', $template, $match)) {
305 2
            $imports = explode("\n", trim($match['imports']));
306 2
            sort($imports);
307
308 2
            return str_replace(trim($match['imports']), implode("\n", $imports), $template);
309
        }
310
311 2
        return $template;
312
    }
313
314
    /**
315
     * Construit le chemin du fichier à partir du nom de la classe.
316
     */
317
    protected function buildPath(string $class): string
318
    {
319 2
        $namespace = trim(str_replace('/', '\\', $this->option('namespace', APP_NAMESPACE)), '\\');
320
321
		// Vérifier que le namespace est réellement défini et que nous ne sommes pas en train de taper du charabia.
322 2
        $base = service('autoloader')->getNamespace($namespace);
323
324
        if (! $base = reset($base)) {
325 2
            $this->io->error(lang('CLI.namespaceNotDefined', [$namespace]), true);
326
327
            return '';
328
        }
329
330 2
        $base = realpath($base) ?: $base;
331 2
        $file = $base . DS . $this->formatFilename($namespace, $class) . '.php';
332
333 2
        return implode(DS, array_slice(explode(DS, $file), 0, -1)) . DS . $this->basename($file);
334
    }
335
336
    /**
337
     * Recupere le namespace a partir de l'option du cli ou le namespace par defaut si l'option n'est pas defini.
338
     * Peut etre directement modifier directement par la propriete $this->namespace.
339
     */
340
    protected function getNamespace(): string
341
    {
342 2
        return $this->namespace ?? trim(str_replace('/', '\\', $this->option('namespace') ?? APP_NAMESPACE), '\\');
343
    }
344
345
346
    /**
347
     * Permet aux générateurs enfants de modifier le drapeau interne `$hasClassName`.
348
     */
349
    protected function setHasClassName(bool $hasClassName): self
350
    {
351
        $this->hasClassName = $hasClassName;
352
353
        return $this;
354
    }
355
356
    /**
357
     * Permet aux générateurs enfants de modifier le drapeau interne `$sortImports`.
358
     */
359
    protected function setSortImports(bool $sortImports): self
360
    {
361
        $this->sortImports = $sortImports;
362
363
        return $this;
364
    }
365
366
    /**
367
     * Permet aux générateurs enfants de modifier le drapeau interne `$enabledSuffixing`.
368
     */
369
    protected function setEnabledSuffixing(bool $enabledSuffixing): self
370
    {
371
        $this->enabledSuffixing = $enabledSuffixing;
372
373
        return $this;
374
    }
375
376
    /**
377
     * Normaliser le nom de classe entrée.
378
     */
379
    private function normalizeInputClassName(): string
380
    {
381
		// Obtient le nom de la classe à partir de l'entrée.
382 2
        $class = $this->params[0] ?? $this->params['name'] ?? null;
383
384
        if ($class === null && $this->hasClassName) {
385
            // @codeCoverageIgnoreStart
386
            $nameLang = $this->classNameLang ?: 'CLI.generator.className.default';
387
            $class    = $this->prompt(lang($nameLang));
0 ignored issues
show
Bug introduced by
It seems like prompt() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

387
            /** @scrutinizer ignore-call */ 
388
            $class    = $this->prompt(lang($nameLang));
Loading history...
388
            $this->eol();
389
            // @codeCoverageIgnoreEnd
390
        }
391
392 2
        helper('inflector');
393
394 2
        $component = singular($this->component);
395
396
        /**
397
         * @see https://regex101.com/r/a5KNCR/1
398
         */
399 2
        $pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)$/i', $component);
400
401
        if (preg_match($pattern, $class, $matches) === 1) {
402 2
            $class = $matches[1] . ucfirst($matches[2]);
403
        }
404
405 2
		$suffix = $this->option('suffix') ?? array_key_exists('suffix', $this->params);
406
407
        if ($this->enabledSuffixing && $suffix && preg_match($pattern, $class) !== 1) {
408 2
            $class .= ucfirst($component);
409
        }
410
411
        // Coupe l'entrée, normalise les séparateurs et s'assure que tous les chemins sont en Pascalcase.
412 2
        return ltrim(implode('\\', array_map(pascalize(...), explode('\\', str_replace('/', '\\', trim($class))))), '\\/');
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Cli\Traits\pascalize was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
413
    }
414
}
415