MakeModelCommand::updateRegistrationClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 29
nc 1
nop 2
dl 0
loc 41
rs 9.456
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
3
namespace Leonidas\Console\Command;
4
5
use DirectoryIterator;
6
use Leonidas\Console\Command\Abstracts\HopliteCommand;
7
use Leonidas\Console\Library\Printer\Model\ModelComponentFactory;
8
use Leonidas\Console\Library\Printer\Model\PsrPrinterFactory;
9
use Nette\PhpGenerator\PhpFile;
10
use Symfony\Component\Console\Input\InputArgument;
11
use Symfony\Component\Console\Input\InputOption;
12
13
class MakeModelCommand extends HopliteCommand
14
{
15
    public const VALID_TEMPLATES = [
16
        'post',
17
        'post:h',
18
        'attachment',
19
        'term',
20
        'term:h',
21
        'user',
22
        // 'comment',
23
    ];
24
25
    protected const CORE_FILE_METHODS = [
26
        'model' => 'makeModelFiles',
27
        'collection' => 'makeCollectionFiles',
28
        'repository' => 'makeRepositoryFiles',
29
    ];
30
31
    protected const SUPPORT_FILE_METHODS = [
32
        'factories' => 'makeFactoryFiles',
33
        'access' => 'makeAccessProviderFiles',
34
        'facade' => 'makeFacadeFiles',
35
    ];
36
37
    protected const EXTRA_FILE_METHODS = [
38
        'registration' => 'updateRegistrationClass',
39
    ];
40
41
    protected static $defaultName = 'make:model';
42
43
    protected function configure()
44
    {
45
        $this
46
            ->addArgument('model', InputArgument::REQUIRED, '')
47
            ->addArgument('entity', InputArgument::REQUIRED, '')
48
            ->addArgument('single', InputArgument::REQUIRED, '')
49
            ->addArgument('plural', InputArgument::REQUIRED, '')
50
            ->addArgument('components', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '')
51
            ->addOption('template', 't', InputOption::VALUE_REQUIRED, '', 'post')
52
            ->addOption('namespace', 's', InputOption::VALUE_REQUIRED, '')
53
            ->addOption('contracts', 'c', InputOption::VALUE_REQUIRED, '')
54
            ->addOption('facades', 'x', InputOption::VALUE_REQUIRED, '')
55
            ->addOption('abstracts', 'a', InputOption::VALUE_NONE, '')
56
            ->addOption('bootstrap', 'r', InputOption::VALUE_REQUIRED, '')
57
            ->addOption('design', 'd', InputOption::VALUE_NONE, 'Generate interfaces only')
58
            ->addOption('build', 'b', InputOption::VALUE_NONE, 'Generate classes using interfaces as guide')
59
            ->addOption('omit', 'o', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, '')
60
            ->addOption('force', 'f', InputOption::VALUE_NONE, '');
61
    }
62
63
    protected function handle(): int
64
    {
65
        $template = $this->input->getOption('template');
66
67
        if ($this->isValidTemplate($template)) {
68
            $status = $this->makeFiles($template);
69
        } else {
70
            $this->output->error("Value \"{$template}\" is not an accepted value for template");
71
72
            $status = self::FAILURE;
73
        }
74
75
        return $status;
76
    }
77
78
    protected function isValidTemplate(string $template): bool
79
    {
80
        return in_array($template, static::VALID_TEMPLATES);
81
    }
82
83
    protected function makeFiles(string $template): int
84
    {
85
        $model = $this->convert($this->input->getArgument('model'))->toPascal();
86
        $namespace = $this->configurableOption('namespace', 'make.model.namespace');
87
        $contracts = $this->configurableOption('contracts', 'make.model.contracts');
88
        $facades = $this->configurableOption('facades', 'facades');
89
90
        $factory = $this->getComponentFactory($model, $namespace, $contracts, $facades, $template);
0 ignored issues
show
Bug introduced by
It seems like $namespace can also be of type null; however, parameter $namespace of Leonidas\Console\Command...::getComponentFactory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

90
        $factory = $this->getComponentFactory($model, /** @scrutinizer ignore-type */ $namespace, $contracts, $facades, $template);
Loading history...
Bug introduced by
It seems like $contracts can also be of type null; however, parameter $contracts of Leonidas\Console\Command...::getComponentFactory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

90
        $factory = $this->getComponentFactory($model, $namespace, /** @scrutinizer ignore-type */ $contracts, $facades, $template);
Loading history...
Bug introduced by
It seems like $facades can also be of type null; however, parameter $facades of Leonidas\Console\Command...::getComponentFactory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

90
        $factory = $this->getComponentFactory($model, $namespace, $contracts, /** @scrutinizer ignore-type */ $facades, $template);
Loading history...
91
        $paths = $this->getOutputPaths($model, $namespace, $contracts, $facades);
0 ignored issues
show
Bug introduced by
It seems like $contracts can also be of type null; however, parameter $contracts of Leonidas\Console\Command...mmand::getOutputPaths() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

91
        $paths = $this->getOutputPaths($model, $namespace, /** @scrutinizer ignore-type */ $contracts, $facades);
Loading history...
Bug introduced by
It seems like $namespace can also be of type null; however, parameter $namespace of Leonidas\Console\Command...mmand::getOutputPaths() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

91
        $paths = $this->getOutputPaths($model, /** @scrutinizer ignore-type */ $namespace, $contracts, $facades);
Loading history...
Bug introduced by
It seems like $facades can also be of type null; however, parameter $facades of Leonidas\Console\Command...mmand::getOutputPaths() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

91
        $paths = $this->getOutputPaths($model, $namespace, $contracts, /** @scrutinizer ignore-type */ $facades);
Loading history...
92
        $action = $this->resolveRequestedAction();
93
94
        $force = $this->input->getOption('force');
95
96
        foreach ($this->resolveComponents('core') as $method) {
97
            $status = $this->$method($factory, $action, $paths, $force);
98
99
            if (self::SUCCESS !== $status) {
100
                return $status;
101
            }
102
        }
103
104
        if ('interfaces' !== $action) {
105
            foreach ($this->resolveComponents('support') as $method) {
106
                $status = $this->$method($factory, $paths, $force);
107
108
                if (self::SUCCESS !== $status) {
109
                    return $status;
110
                }
111
            }
112
113
            foreach ($this->resolveComponents('extra') as $method) {
114
                $status = $this->$method($factory, $force);
115
116
                if (self::SUCCESS !== $status) {
117
                    return $status;
118
                }
119
            }
120
        }
121
122
        $this->output->success("Successfully created model files");
123
124
        return self::SUCCESS;
125
    }
126
127
    protected function resolveComponents(string $set): array
128
    {
129
        $set = constant(
130
            sprintf('%s::%s_FILE_METHODS', static::class, strtoupper($set))
131
        );
132
133
        $selected = $this->input->getArgument('components') ?: array_keys($set);
134
        $omitted = $this->input->getOption('omit') ?? [];
135
136
        $resolved = [];
137
138
        foreach ($set as $component => $method) {
139
            if (in_array($component, $selected) && !in_array($component, $omitted)) {
140
                $resolved[] = $method;
141
            }
142
        }
143
144
        return $resolved;
145
    }
146
147
    protected function getComponentFactory(
148
        string $model,
149
        string $namespace,
150
        string $contracts,
151
        string $facades,
152
        string $template
153
    ): ModelComponentFactory {
154
        $entity = $this->input->getArgument('entity');
155
        $single = $this->input->getArgument('single');
156
        $plural = $this->input->getArgument('plural');
157
158
        $namespace = $this->pathToNamespace($namespace, $model);
159
        $contracts = $this->pathToNamespace($contracts, $model);
160
        $abstracts = $this->resolveAbstractNamespace($namespace);
161
        $facades = $this->pathToNamespace($facades);
162
163
        return ModelComponentFactory::build([
164
            'model' => $model,
165
            'namespace' => $namespace,
166
            'contracts' => $contracts,
167
            'abstracts' => $abstracts,
168
            'facades' => $facades,
169
            'entity' => $entity,
170
            'single' => $single,
171
            'plural' => $plural,
172
            'template' => $template,
173
        ]);
174
    }
175
176
    protected function getOutputPaths(string $model, string $namespace, string $contracts, string $facades): array
177
    {
178
        return [
179
            'interfaces' => $contracts . DIRECTORY_SEPARATOR . $model,
180
            'classes' => $namespace = $namespace . DIRECTORY_SEPARATOR . $model,
181
            'abstracts' => $this->resolveAbstractDir($namespace),
182
            'facades' => $facades,
183
        ];
184
    }
185
186
    protected function setupTestDir(): array
187
    {
188
        $playground = $this->external('/.playground/model');
189
190
        if ($this->filesystem->exists($playground)) {
191
            foreach (new DirectoryIterator($playground) as $file) {
192
                if (!$file->isFile()) {
193
                    continue;
194
                }
195
196
                $isInterface = str_ends_with($file->getBasename(), 'Interface.php');
197
                $isRegistrar = $file->getBasename() === 'RegisterModelServices.php';
198
199
                if (!($isInterface || $isRegistrar)) {
200
                    $this->filesystem->remove($file->getPathname());
201
                }
202
203
                if ($isInterface) {
204
                    require $file->getPathname();
205
                }
206
            }
207
208
        // $this->filesystem->remove($playground);
209
        } else {
210
            $this->filesystem->mkdir($playground);
211
        }
212
213
        $registrar = 'RegisterModelServices.php';
214
215
        $this->filesystem->copy(
216
            $this->external("/src/Framework/Bootstrap/{$registrar}"),
217
            "{$playground}/{$registrar}"
218
        );
219
220
        foreach (['Contracts', 'Library'] as $namespace) {
221
            $this->filesystem->remove(
222
                $this->external('/src/' . $namespace . '/System/Model/Test')
223
            );
224
        }
225
226
        return [
227
            'interfaces' => $playground,
228
            'classes' => $playground,
229
            'abstracts' => $playground,
230
            'facades' => $playground,
231
        ];
232
    }
233
234
    protected function resolveRequestedAction(): string
235
    {
236
        if ($this->input->getOption('design')) {
237
            return 'interfaces';
238
        } elseif ($this->input->getOption('build')) {
239
            return 'classes';
240
        } else {
241
            return 'complete';
242
        }
243
    }
244
245
    protected function makeModelFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
246
    {
247
        $interface = $factory->getModelInterfacePrinter();
248
        $class = $factory->getModelPrinter();
249
250
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
251
        $classFile = $this->phpFile($paths['classes'], $class->getClass());
252
253
        if ('interfaces' === $action || 'complete' === $action) {
254
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
255
        } elseif ('classes' === $action) {
256
            if (!interface_exists($interfaceFqn = $interface->getClassFqn())) {
257
                $this->output->error("Interface {$interfaceFqn} does not exist");
258
259
                return self::INVALID;
260
            }
261
262
            $this->writeFile($classFile, $class->printFromType(), $force);
263
        }
264
265
        if ('complete' === $action) {
266
            $this->writeFile($classFile, $class->printFile(), $force);
267
        }
268
269
        return self::SUCCESS;
270
    }
271
272
    protected function makeCollectionFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
273
    {
274
        $isPost = $factory->isPostTemplate();
275
276
        $interface = $factory->getCollectionInterfacePrinter();
277
        $collection = $isPost
278
            ? $factory->getChildCollectionPrinter()
279
            : $factory->getCollectionPrinter();
280
        $abstract = $factory->getAbstractCollectionPrinter();
281
        $query = $factory->getChildQueryPrinter();
282
283
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
284
        $collectionFile = $this->phpFile($paths['classes'], $collection->getClass());
285
        $abstractFile = $this->phpFile($paths['abstracts'], $abstract->getClass());
286
        $queryFile = $this->phpFile($paths['classes'], $query->getClass());
287
288
        if ('interfaces' === $action || 'complete' === $action) {
289
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
290
        } elseif ('classes' === $action) {
291
            if (!interface_exists($interface->getClassFqn())) {
292
                $this->output->error("Interface {$interface->getClassFqn()} does not exist");
293
294
                return self::INVALID;
295
            }
296
297
            if ($isPost) {
298
                $this->writeFile($abstractFile, $abstract->printFromType(), $force);
299
                $this->writeFile($collectionFile, $collection->printFile(), $force);
300
                $this->writeFile($queryFile, $query->printFile(), $force);
301
            } else {
302
                $this->writeFile($collectionFile, $collection->printFromType(), $force);
0 ignored issues
show
Bug introduced by
The method printFromType() does not exist on Leonidas\Console\Library...ollectionAsChildPrinter. ( Ignorable by Annotation )

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

302
                $this->writeFile($collectionFile, $collection->/** @scrutinizer ignore-call */ printFromType(), $force);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
303
            }
304
        }
305
306
        if ('complete' === $action) {
307
            $this->writeFile($collectionFile, $collection->printFile(), $force);
308
309
            if ($isPost) {
310
                $this->writeFile($abstractFile, $abstract->printFile(), $force);
311
                $this->writeFile($queryFile, $query->printFile(), $force);
312
            }
313
        }
314
315
        return self::SUCCESS;
316
    }
317
318
    protected function makeRepositoryFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
319
    {
320
        $interface = $factory->getRepositoryInterfacePrinter();
321
        $class = $factory->getRepositoryPrinter();
322
323
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
324
        $classFile = $this->phpFile($paths['classes'], $class->getClass());
325
326
        if ('interfaces' === $action || 'complete' === $action) {
327
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
328
        } elseif ('classes' === $action) {
329
            if (!interface_exists($interfaceFqn = $interface->getClassFqn())) {
330
                $this->output->error("Interface {$interfaceFqn} does not exist");
331
332
                return self::INVALID;
333
            }
334
335
            $this->writeFile($classFile, $class->printFromType(), $force);
336
        }
337
338
        if ('complete' === $action) {
339
            $this->writeFile($classFile, $class->printFile(), $force);
340
        }
341
342
        return self::SUCCESS;
343
    }
344
345
    protected function makeFactoryFiles(ModelComponentFactory $factory, array $paths, bool $force): int
346
    {
347
        $model = $factory->getModelConverterPrinter();
348
        $collection = $factory->getCollectionFactoryPrinter();
349
350
        $modelFile = $this->phpFile($paths['classes'], $model->getClass());
351
        $collectionFile = $this->phpFile($paths['classes'], $collection->getClass());
352
353
        $this->writeFile($modelFile, $model->printFile(), $force);
354
        $this->writeFile($collectionFile, $collection->printFile(), $force);
355
356
        if ($factory->isPostTemplate()) {
357
            $query = $factory->getQueryFactoryPrinter();
358
            $queryFile = $this->phpFile($paths['classes'], $query->getClass());
359
360
            $this->writeFile($queryFile, $query->printFile(), $force);
361
        }
362
363
        return self::SUCCESS;
364
    }
365
366
    protected function makeAccessProviderFiles(ModelComponentFactory $factory, array $paths, bool $force): int
367
    {
368
        $get = $factory->getGetAccessProviderPrinter();
369
        $set = $factory->getSetAccessProviderPrinter();
370
371
        $getFile = $this->phpFile($paths['classes'], $get->getClass());
372
        $setFile = $this->phpFile($paths['classes'], $set->getClass());
373
374
        $this->writeFile($getFile, $get->printFile(), $force);
375
        $this->writeFile($setFile, $set->printFile(), $force);
376
377
        if ($factory->isPostTemplate()) {
378
            $tag = $factory->getTagAccessProviderPrinter();
379
            $tagFile = $this->phpFile($paths['classes'], $tag->getClass());
380
381
            $this->writeFile($tagFile, $tag->printFile(), $force);
382
        }
383
384
        return self::SUCCESS;
385
    }
386
387
    protected function makeFacadeFiles(ModelComponentFactory $factory, array $paths, bool $force): int
388
    {
389
        $facade = $factory->getRepositoryFacadePrinter();
390
        $facadeFile = $this->phpFile($paths['facades'], $facade->getClass());
391
392
        $this->writeFile($facadeFile, $facade->printFromType(), $force);
393
394
        return self::SUCCESS;
395
    }
396
397
    protected function updateRegistrationClass(ModelComponentFactory $factory, bool $force): int
398
    {
399
        $name = $factory->getSingle();
400
        $model = $factory->getModelPrinter();
401
        $schema = [
402
            'post' => 'post',
403
            'post:h' => 'post',
404
            'attachment' => 'attachment',
405
            'term' => 'term',
406
            'term:h' => 'term',
407
            'user' => 'user',
408
            // 'comment' => 'comment',
409
        ][$factory->getTemplate()];
410
411
        $registrar = $this->configurableOption('bootstrap', 'make.model.bootstrap');
412
        $file = PhpFile::fromCode(file_get_contents($registrar));
0 ignored issues
show
Bug introduced by
It seems like $registrar can also be of type null; however, parameter $filename of file_get_contents() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

412
        $file = PhpFile::fromCode(file_get_contents(/** @scrutinizer ignore-type */ $registrar));
Loading history...
413
414
        $namespaces = $file->getNamespaces();
415
        $namespace = reset($namespaces);
416
417
        $namespace->addUse($model->getClassFqn());
418
419
        $classes = $namespace->getClasses();
420
        $class = reset($classes);
421
422
        $class->addMethod($name . 'Services')
423
            ->setReturnType('void')
424
            ->setBody(sprintf(
425
                "\$this->register(%s::class, '%s', '%s');",
426
                $model->getClass(),
427
                $factory->getEntity(),
428
                $schema
429
            ));
430
431
        $this->writeFile(
432
            $registrar,
0 ignored issues
show
Bug introduced by
It seems like $registrar can also be of type null; however, parameter $path of Leonidas\Console\Command...iteCommand::writeFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

432
            /** @scrutinizer ignore-type */ $registrar,
Loading history...
433
            PsrPrinterFactory::create()->printFile($file),
434
            true
435
        );
436
437
        return self::SUCCESS;
438
    }
439
}
440