MakeDtoCommand::shouldBeAddedToUseStatements()   A
last analyzed

Complexity

Conditions 6
Paths 13

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 5
c 1
b 0
f 0
nc 13
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 6
rs 9.2222
1
<?php
2
3
namespace Cerbero\LaravelDto\Console\Commands;
4
5
use Cerbero\LaravelDto\Console\DtoGenerationData;
6
use Cerbero\LaravelDto\Console\Manifest;
7
use Cerbero\LaravelDto\Console\ModelPropertiesMapper;
8
use Cerbero\LaravelDto\Console\DtoQualifierContract;
9
use Illuminate\Console\GeneratorCommand;
10
use Illuminate\Database\Eloquent\Model;
11
use Illuminate\Filesystem\Filesystem;
12
use Symfony\Component\Console\Exception\InvalidArgumentException;
13
14
/**
15
 * The Artisan command to generate DTOs.
16
 *
17
 */
18
class MakeDtoCommand extends GeneratorCommand
19
{
20
    /**
21
     * The console command description.
22
     *
23
     * @var string
24
     */
25
    protected $description = 'Create a new DTO class';
26
27
    /**
28
     * The name and signature of the console command.
29
     *
30
     * @var string
31
     */
32
    protected $signature = 'make:dto
33
        {name : The model to create the DTO for, e.g. App/User}
34
        {--f|force : Create the class even if the DTO already exists}';
35
36
    /**
37
     * The models use statements.
38
     *
39
     * @var array
40
     */
41
    protected $useStatements = [];
42
43
    /**
44
     * The DTO class qualifier.
45
     *
46
     * @var DtoQualifierContract
47
     */
48
    protected $qualifier;
49
50
    /**
51
     * The DTO generation manifest.
52
     *
53
     * @var Manifest
54
     */
55
    protected $manifest;
56
57
    /**
58
     * Instantiate the class.
59
     *
60
     * @param Filesystem $files
61
     * @param DtoQualifierContract $qualifier
62
     * @param Manifest $manifest
63
     */
64 8
    public function __construct(Filesystem $files, DtoQualifierContract $qualifier, Manifest $manifest)
65
    {
66 8
        parent::__construct($files);
67
68 8
        $this->qualifier = $qualifier;
69 8
        $this->manifest = $manifest;
70 8
    }
71
72
    /**
73
     * Execute the console command.
74
     *
75
     * @return bool|null
76
     *
77
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
78
     */
79 2
    public function handle()
80
    {
81 2
        parent::handle();
82
83 2
        $this->manifest->delete();
84 2
    }
85
86
    /**
87
     * Get the stub file for the generator.
88
     *
89
     * @return string
90
     */
91 2
    protected function getStub(): string
92
    {
93 2
        return __DIR__ . '/../stubs/dto.stub';
94
    }
95
96
    /**
97
     * Parse the class name and format according to the root namespace.
98
     *
99
     * @param string $name
100
     * @return string
101
     */
102 2
    protected function qualifyClass($name): string
103
    {
104 2
        return $this->type = $this->qualifier->qualify($name);
105
    }
106
107
    /**
108
     * Get the desired class name from the input.
109
     *
110
     * @return string
111
     */
112 2
    protected function getNameInput(): string
113
    {
114 2
        $model = str_replace('/', '\\', parent::getNameInput());
115
116 2
        if (is_subclass_of($model, Model::class)) {
117 2
            return $model;
118
        }
119
120
        throw new InvalidArgumentException("Invalid model [$model]");
121
    }
122
123
    /**
124
     * Build the class with the given name.
125
     *
126
     * @param string $name
127
     * @return string
128
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
129
     */
130 2
    protected function buildClass($name): string
131
    {
132 2
        $this->manifest->addStartingDto($name)->addGeneratingDto($name)->save();
133
134 2
        $search = ['DummyModel', 'DummyProperties', 'DummyUseStatements'];
135 2
        $replace = [class_basename($this->getNameInput()), $this->getModelProperties(), $this->getUseStatements()];
136 2
        $content = parent::buildClass($name);
137
138 2
        return $this->sortUseStatements(str_replace($search, $replace, $content));
139
    }
140
141
    /**
142
     * Retrieve the given model properties
143
     *
144
     * @return string
145
     */
146 2
    protected function getModelProperties(): string
147
    {
148 2
        $properties = '';
149 2
        $map = $this->laravel->make(ModelPropertiesMapper::class)->map($this->getCommandDto());
150 2
        $this->type = $this->manifest->getGeneratingDto();
151
152 2
        foreach ($map as $name => $types) {
153 2
            foreach ($types as &$type) {
154 2
                $normalizedType = rtrim($type, '[]');
155
156 2
                if ($this->shouldBeAddedToUseStatements($normalizedType)) {
157 2
                    $this->useStatements[$normalizedType] = true;
158
                }
159
160 2
                $type = class_basename($type);
161
            }
162
163 2
            $type = implode('|', $types);
164 2
            $properties .= " * @property {$type} \${$name}\n";
165
        }
166
167 2
        return rtrim($properties);
168
    }
169
170
    /**
171
     * Retrieve the DTO generation data
172
     *
173
     * @return DtoGenerationData
174
     */
175 2
    protected function getCommandDto(): DtoGenerationData
176
    {
177 2
        return DtoGenerationData::make([
178 2
            'model_class' => $model = $this->getNameInput(),
179 2
            'model' => new $model(),
180 2
            'forced' => $this->option('force') ?: false,
181 2
            'output' => $this->getOutput(),
182
        ]);
183
    }
184
185
    /**
186
     * Determine whether the given type should be added to use statements
187
     *
188
     * @param string $type
189
     * @return bool
190
     */
191 2
    protected function shouldBeAddedToUseStatements(string $type): bool
192
    {
193 2
        if ($this->hasSameNamespace($type)) {
194 2
            return false;
195
        }
196
197 2
        $usesStartingDto = $this->manifest->isStartingDto($type) && !$this->manifest->isGenerating($type);
198 2
        $generatedOrGenerating = $this->manifest->generated($type) || $this->manifest->generating($type);
199
200 2
        return $usesStartingDto || $generatedOrGenerating || class_exists($type);
201
    }
202
203
    /**
204
     * Determine whether the given class has the same namespace of the DTO that is going to be generated
205
     *
206
     * @param string $class
207
     * @return bool
208
     */
209 2
    protected function hasSameNamespace(string $class): bool
210
    {
211 2
        $segmentsClass = explode('\\', $class);
212 2
        $segmentsDto = explode('\\', $this->manifest->getGeneratingDto());
0 ignored issues
show
Bug introduced by
It seems like $this->manifest->getGeneratingDto() 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

212
        $segmentsDto = explode('\\', /** @scrutinizer ignore-type */ $this->manifest->getGeneratingDto());
Loading history...
213
214 2
        array_pop($segmentsClass);
215 2
        array_pop($segmentsDto);
216
217 2
        return $segmentsClass === $segmentsDto;
218
    }
219
220
    /**
221
     * Retrieve the use statements
222
     *
223
     * @return string|null
224
     */
225 2
    protected function getUseStatements(): ?string
226
    {
227 2
        $useStatements = '';
228
229 2
        foreach ($this->useStatements as $class => $value) {
230 2
            $useStatements .= "\nuse {$class};";
231 2
            unset($this->useStatements[$class]);
232
        }
233
234 2
        return $useStatements;
235
    }
236
237
    /**
238
     * Previous versions of Laravel didn't sort imports.
239
     * Overriding this method and using sortUseStatements() instead keeps generated DTOs consistent across all versions
240
     *
241
     * @param string $stub
242
     * @return string
243
     */
244 2
    protected function sortImports($stub)
245
    {
246 2
        return $stub;
247
    }
248
249
    /**
250
     * Alphabetically sorts the use statements for the given stub
251
     *
252
     * @param string $stub
253
     * @return string
254
     */
255 2
    protected function sortUseStatements($stub)
256
    {
257 2
        preg_match('/(?P<imports>(?:use [^;]+;$\n?)+)/m', $stub, $match);
258
259 2
        $imports = explode("\n", trim($match['imports']));
260
261 2
        sort($imports);
262
263 2
        return str_replace(trim($match['imports']), implode("\n", $imports), $stub);
264
    }
265
}
266