Test Failed
Push — master ( 37e761...633337 )
by Chris
18:21
created

MakeModelCommand::updateRegistrationClass()   A

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
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 41
ccs 0
cts 0
cp 0
crap 2
rs 9.456
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
    ];
23
24
    protected const CORE_FILE_METHODS = [
25
        'model' => 'makeModelFiles',
26
        'collection' => 'makeCollectionFiles',
27
        'repository' => 'makeRepositoryFiles',
28
    ];
29
30
    protected const SUPPORT_FILE_METHODS = [
31
        'factories' => 'makeFactoryFiles',
32
        'access' => 'makeAccessProviderFiles',
33
        'registration' => 'updateRegistrationClass',
34
        'facade' => 'makeFacadeFiles',
35
    ];
36
37
    protected static $defaultName = 'make:model';
38
39
    protected function configure()
40
    {
41
        $this
42
            ->addArgument('model', InputArgument::REQUIRED, '')
43
            ->addArgument('entity', InputArgument::REQUIRED, '')
44
            ->addArgument('single', InputArgument::REQUIRED, '')
45
            ->addArgument('plural', InputArgument::REQUIRED, '')
46
            ->addArgument('components', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '')
47
            ->addOption('namespace', 's', InputOption::VALUE_OPTIONAL, '')
48
            ->addOption('contracts', 'c', InputOption::VALUE_OPTIONAL, '')
49
            ->addOption('abstracts', 'a', InputOption::VALUE_NONE, '')
50
            ->addOption('template', 't', InputOption::VALUE_REQUIRED, '', 'post')
51
            ->addOption('registration', 'r', InputOption::VALUE_REQUIRED, '')
52
            ->addOption('facade', 'x', InputOption::VALUE_REQUIRED, '')
53
            ->addOption('design', 'd', InputOption::VALUE_NONE, 'Generate interfaces only')
54
            ->addOption('build', 'b', InputOption::VALUE_NONE, 'Generate classes using interfaces as guide')
55
            ->addOption('force', 'f', InputOption::VALUE_NONE, '');
56
    }
57
58
    protected function handle(): int
59
    {
60
        $template = $this->input->getOption('template');
61
62
        if ($this->isValidTemplate($template)) {
63
            $status = $this->makeFiles($template);
64
        } else {
65
            $this->output->error("Value \"{$template}\" is not an accepted value for template");
66
67
            $status = self::FAILURE;
68
        }
69
70
        return $status;
71
    }
72
73
    protected function isValidTemplate(string $template): bool
74
    {
75
        return in_array($template, static::VALID_TEMPLATES);
76
    }
77
78
    protected function makeFiles(string $template): int
79
    {
80
        $model = $this->convert($this->input->getArgument('model'))->toPascal();
81
        $namespace = $this->configurableOption('namespace', 'make.model.namespace');
82
        $contracts = $this->configurableOption('contracts', 'make.model.contracts');
83
84
        $factory = $this->getComponentFactory($model, $namespace, $contracts, $template);
0 ignored issues
show
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

84
        $factory = $this->getComponentFactory($model, $namespace, /** @scrutinizer ignore-type */ $contracts, $template);
Loading history...
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

84
        $factory = $this->getComponentFactory($model, /** @scrutinizer ignore-type */ $namespace, $contracts, $template);
Loading history...
85
        $paths = $this->getOutputPaths($model, $namespace, $contracts);
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

85
        $paths = $this->getOutputPaths($model, $namespace, /** @scrutinizer ignore-type */ $contracts);
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

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

280
                $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...
281
            }
282
        }
283
284
        if ('complete' === $action) {
285
            $this->writeFile($collectionFile, $collection->printFile(), $force);
286
287
            if ($isPost) {
288
                $this->writeFile($abstractFile, $abstract->printFile(), $force);
289
                $this->writeFile($queryFile, $query->printFile(), $force);
290
            }
291
        }
292
293
        return self::SUCCESS;
294
    }
295
296
    protected function makeRepositoryFiles(ModelComponentFactory $factory, string $action, array $paths, bool $force): int
297
    {
298
        $interface = $factory->getRepositoryInterfacePrinter();
299
        $class = $factory->getRepositoryPrinter();
300
301
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
302
        $classFile = $this->phpFile($paths['classes'], $class->getClass());
303
304
        if ('interfaces' === $action || 'complete' === $action) {
305
            $this->writeFile($interfaceFile, $interface->printFile(), $force);
306
        } elseif ('classes' === $action) {
307
            if (!interface_exists($interfaceFqn = $interface->getClassFqn())) {
308
                $this->output->error("Interface {$interfaceFqn} does not exist");
309
310
                return self::INVALID;
311
            }
312
313
            $this->writeFile($classFile, $class->printFromType(), $force);
314
        }
315
316
        if ('complete' === $action) {
317
            $this->writeFile($classFile, $class->printFile(), $force);
318
        }
319
320
        return self::SUCCESS;
321
    }
322
323
    protected function makeFactoryFiles(ModelComponentFactory $factory, array $paths, bool $force): int
324
    {
325
        $model = $factory->getModelConverterPrinter();
326
        $collection = $factory->getCollectionFactoryPrinter();
327
328
        $modelFile = $this->phpFile($paths['classes'], $model->getClass());
329
        $collectionFile = $this->phpFile($paths['classes'], $collection->getClass());
330
331
        $this->writeFile($modelFile, $model->printFile(), $force);
332
        $this->writeFile($collectionFile, $collection->printFile(), $force);
333
334
        if ($factory->isPostTemplate()) {
335
            $query = $factory->getQueryFactoryPrinter();
336
            $queryFile = $this->phpFile($paths['classes'], $query->getClass());
337
338
            $this->writeFile($queryFile, $query->printFile(), $force);
339
        }
340
341
        return self::SUCCESS;
342
    }
343
344
    protected function makeAccessProviderFiles(ModelComponentFactory $factory, array $paths, bool $force): int
345
    {
346
        $get = $factory->getGetAccessProviderPrinter();
347
        $set = $factory->getSetAccessProviderPrinter();
348
349
        $getFile = $this->phpFile($paths['classes'], $get->getClass());
350
        $setFile = $this->phpFile($paths['classes'], $set->getClass());
351
352
        $this->writeFile($getFile, $get->printFile(), $force);
353
        $this->writeFile($setFile, $set->printFile(), $force);
354
355
        if ($factory->isPostTemplate()) {
356
            $tag = $factory->getTagAccessProviderPrinter();
357
            $tagFile = $this->phpFile($paths['classes'], $tag->getClass());
358
359
            $this->writeFile($tagFile, $tag->printFile(), $force);
360
        }
361
362
        return self::SUCCESS;
363
    }
364
365
    protected function updateRegistrationClass(ModelComponentFactory $factory, array $paths, bool $force): int
366
    {
367
        $name = $factory->getSingle();
368
        $model = $factory->getModelPrinter();
369
        $schema = [
370
            'post' => 'post',
371
            'post:h' => 'post',
372
            'attachment' => 'attachment',
373
            'term' => 'term',
374
            'term:h' => 'term',
375
            'user' => 'user',
376
            // 'comment' => 'comment',
377
        ][$factory->getTemplate()];
378
379
        $registrar = $this->configurableOption('registration', 'make.model.registration');
380
        $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

380
        $file = PhpFile::fromCode(file_get_contents(/** @scrutinizer ignore-type */ $registrar));
Loading history...
381
382
        $namespaces = $file->getNamespaces();
383
        $namespace = reset($namespaces);
384
385
        $namespace->addUse($model->getClassFqn());
386
387
        $classes = $namespace->getClasses();
388
        $class = reset($classes);
389
390
        $class->addMethod($name . 'Services')
391
            ->setReturnType('void')
392
            ->setBody(sprintf(
393
                "\$this->register(%s::class, '%s', '%s');",
394
                $model->getClass(),
395
                $factory->getEntity(),
396
                $schema
397
            ));
398
399
        $this->writeFile(
400
            $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

400
            /** @scrutinizer ignore-type */ $registrar,
Loading history...
401
            PsrPrinterFactory::create()->printFile($file),
402
            true
403
        );
404
405
        return self::SUCCESS;
406
    }
407
408
    protected function makeFacadeFiles(ModelComponentFactory $factory, array $paths, bool $force): int
409
    {
410
        $baseFile = $this->configurableOption('facade', 'facade');
411
        $parts = explode('/', $baseFile);
0 ignored issues
show
Bug introduced by
It seems like $baseFile can also be of type null; however, parameter $string of explode() 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

411
        $parts = explode('/', /** @scrutinizer ignore-type */ $baseFile);
Loading history...
412
        $base = str_replace('.php', '', array_pop($parts));
413
        $facadeNamespace = $this->pathToNamespace(implode('/', $parts));
414
        $baseFacade = $facadeNamespace . '\\' . $base;
415
        $facade = $this->convert($factory->getPlural())->toPascal();
416
        $facadeFile = $this->phpFile(implode('/', $parts), $facade);
417
        $repository = $factory->getRepositoryInterfacePrinter();
418
419
        $printer = PsrPrinterFactory::create();
420
        $file = new PhpFile();
421
422
        $namespace = $file->addNamespace($facadeNamespace);
423
424
        $namespace->addUse($baseFacade)->addUse($repository->getClassFqn());
425
426
        $class = $namespace->addClass($facade)->setExtends($baseFacade);
427
428
        if ($factory->isPostTemplate()) {
429
            $query = $factory->getChildQueryPrinter();
430
            $queryFactory = $factory->getQueryFactoryPrinter();
431
432
            $namespace
433
                ->addUse($queryFactory->getClassFqn())
434
                ->addUse($query->getClassFqn());
435
436
            $class->addMethod('fromQuery')
437
                ->setPublic()
438
                ->setStatic(true)
439
                ->setReturnType($query->getClassFqn())
440
                ->setBody(
441
                    'return static::getQueryFactory()->createQuery($GLOBALS[\'wp_query\']);'
442
                );
443
444
            $class->addMethod('getQueryFactory')
445
                ->setProtected()
446
                ->setStatic(true)
447
                ->setReturnType($queryFactory->getClassFqn())
448
                ->setBody(sprintf(
449
                    'return static::$container->get(%s::class);',
450
                    $queryFactory->getClass()
451
                ));
452
        }
453
454
        $class->addMethod('_getFacadeAccessor')
455
            ->setProtected()
456
            ->setStatic(true)
457
            ->setReturnType('string')
458
            ->setBody('return ' . $repository->getClass() . '::class;');
459
460
        $this->writeFile($facadeFile, $printer->printFile($file), $force);
461
462
        return self::SUCCESS;
463
    }
464
}
465