Passed
Push — master ( c8bfb9...3491f1 )
by Chris
15:53
created

ModelCommand::resolveComponents()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 14
ccs 0
cts 7
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Leonidas\Console\Command\Make;
4
5
use DirectoryIterator;
6
use Leonidas\Console\Command\HopliteCommand;
7
use Leonidas\Console\Library\ModelComponentFactory;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputOption;
10
11
class ModelCommand extends HopliteCommand
12
{
13
    protected const CORE_FILE_METHODS = [
14
        'model' => 'makeModelFiles',
15
        'collection' => 'makeCollectionFiles',
16
        'repository' => 'makeRepositoryFiles',
17
    ];
18
19
    protected const SUPPORT_FILE_METHODS = [
20
        'factories' => 'makeFactoryFiles',
21
        'access' => 'makeAccessProviderFiles',
22
    ];
23
24
    protected const VALID_TEMPLATES = [
25
        'post',
26
        'post:h',
27
        'attachment',
28
        'term',
29
        'term:h',
30
        'user',
31
    ];
32
33
    protected static $defaultName = 'make:model';
34
35
    protected function configure()
36
    {
37
        $this
38
            ->addArgument('model', InputArgument::REQUIRED, '')
39
            ->addArgument('entity', InputArgument::REQUIRED, '')
40
            ->addArgument('single', InputArgument::REQUIRED, '')
41
            ->addArgument('plural', InputArgument::REQUIRED, '')
42
            ->addArgument('components', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '')
43
            ->addOption('namespace', 's', InputOption::VALUE_OPTIONAL, '')
44
            ->addOption('contracts', 'c', InputOption::VALUE_OPTIONAL, '')
45
            ->addOption('abstracts', 'a', InputOption::VALUE_NONE, '')
46
            ->addOption('template', 't', InputOption::VALUE_REQUIRED, '', 'post')
47
            ->addOption('design', 'd', InputOption::VALUE_NONE, 'Generate interfaces only')
48
            ->addOption('build', 'b', InputOption::VALUE_NONE, 'Generate classes using interfaces as guide')
49
            ->addOption('replace', 'r', InputOption::VALUE_NONE, '');
50
    }
51
52
    protected function handle(): int
53
    {
54
        $template = $this->input->getOption('template');
55
56
        if ($this->isValidTemplate($template)) {
57
            $status = $this->makeFiles($template);
58
        } else {
59
            $this->output->error("Value \"{$template}\" is not an accepted value for template");
60
61
            $status = self::FAILURE;
62
        }
63
64
        return $status;
65
    }
66
67
    protected function isValidTemplate(string $template): bool
68
    {
69
        return in_array($template, static::VALID_TEMPLATES);
70
    }
71
72
    protected function makeFiles(string $template): int
73
    {
74
        $model = $this->convert($this->input->getArgument('model'))->toPascal();
75
        $namespace = $this->configurableOption('namespace', 'make.model.namespace');
76
        $contracts = $this->configurableOption('contracts', 'make.model.contracts');
77
78
        $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

78
        $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

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

79
        $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

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

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

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...
261
            }
262
        }
263
264
        if ('complete' === $action) {
265
            $this->writeFile($collectionFile, $collection->printFile());
266
267
            if ($isPost) {
268
                $this->writeFile($abstractFile, $abstract->printFile());
269
                $this->writeFile($queryFile, $query->printFile());
270
            }
271
        }
272
273
        return self::SUCCESS;
274
    }
275
276
    protected function makeRepositoryFiles(ModelComponentFactory $factory, string $action, array $paths): int
277
    {
278
        $interface = $factory->getRepositoryInterfacePrinter();
279
        $class = $factory->getRepositoryPrinter();
280
281
        $interfaceFile = $this->phpFile($paths['interfaces'], $interface->getClass());
282
        $classFile = $this->phpFile($paths['classes'], $class->getClass());
283
284
        if ('interfaces' === $action || 'complete' === $action) {
285
            $this->writeFile($interfaceFile, $interface->printFile());
286
        } elseif ('classes' === $action) {
287
            if (!interface_exists($interfaceFqn = $interface->getClassFqn())) {
288
                $this->output->error("Interface {$interfaceFqn} does not exist");
289
290
                return self::INVALID;
291
            }
292
293
            $this->writeFile($classFile, $class->printFromType());
294
        }
295
296
        if ('complete' === $action) {
297
            $this->writeFile($classFile, $class->printFile());
298
        }
299
300
        return self::SUCCESS;
301
    }
302
303
    protected function makeFactoryFiles(ModelComponentFactory $factory, array $paths): int
304
    {
305
        $model = $factory->getModelConverterPrinter();
306
        $collection = $factory->getCollectionFactoryPrinter();
307
308
        $modelFile = $this->phpFile($paths['classes'], $model->getClass());
309
        $collectionFile = $this->phpFile($paths['classes'], $collection->getClass());
310
311
        $this->writeFile($modelFile, $model->printFile());
312
        $this->writeFile($collectionFile, $collection->printFile());
313
314
        if ($factory->isPostTemplate()) {
315
            $query = $factory->getQueryFactoryPrinter();
316
            $queryFile = $this->phpFile($paths['classes'], $query->getClass());
317
318
            $this->writeFile($queryFile, $query->printFile());
319
        }
320
321
        return self::SUCCESS;
322
    }
323
324
    protected function makeAccessProviderFiles(ModelComponentFactory $factory, array $paths): int
325
    {
326
        $get = $factory->getGetAccessProviderPrinter();
327
        $set = $factory->getSetAccessProviderPrinter();
328
329
        $getFile = $this->phpFile($paths['classes'], $get->getClass());
330
        $setFile = $this->phpFile($paths['classes'], $set->getClass());
331
332
        $this->writeFile($getFile, $get->printFile());
333
        $this->writeFile($setFile, $set->printFile());
334
335
        if ($factory->isPostTemplate()) {
336
            $tag = $factory->getTagAccessProviderPrinter();
337
            $tagFile = $this->phpFile($paths['classes'], $tag->getClass());
338
339
            $this->writeFile($tagFile, $tag->printFile());
340
        }
341
342
        return self::SUCCESS;
343
    }
344
}
345