Passed
Push — master ( 57d086...37e761 )
by Chris
14:26
created

MakeModelCommand   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 185
dl 0
loc 334
ccs 0
cts 168
cp 0
rs 8.5599
c 0
b 0
f 0
wmc 48

14 Methods

Rating   Name   Duplication   Size   Complexity  
A isValidTemplate() 0 3 1
A makeAccessProviderFiles() 0 19 2
A resolveRequestedAction() 0 8 3
B makeCollectionFiles() 0 44 9
A makeFactoryFiles() 0 19 2
A makeModelFiles() 0 25 6
A makeFiles() 0 33 6
A makeRepositoryFiles() 0 25 6
A setupTestDir() 0 32 6
A getOutputPaths() 0 6 1
A configure() 0 15 1
A getComponentFactory() 0 23 1
A handle() 0 13 2
A resolveComponents() 0 14 2

How to fix   Complexity   

Complex Class

Complex classes like MakeModelCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MakeModelCommand, and based on these observations, apply Extract Interface, too.

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

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

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