Passed
Push — master ( 864347...46e361 )
by Bruno
09:55
created

ModelGenerator::generateString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 56
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 41
c 0
b 0
f 0
dl 0
loc 56
ccs 0
cts 0
cp 0
rs 9.264
cc 2
nc 2
nop 0
crap 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Modelarium\Laravel\Targets;
4
5
use Formularium\Datatype;
6
use Formularium\Exception\ClassNotFoundException;
7
use Formularium\Formularium;
8
use Formularium\Validator;
9
use Formularium\ValidatorMetadata;
10
use Illuminate\Support\Str;
11
use GraphQL\Type\Definition\ListOfType;
12
use GraphQL\Type\Definition\NonNull;
13
use GraphQL\Type\Definition\ObjectType;
14
use Modelarium\Exception\Exception;
15
use Modelarium\GeneratedCollection;
16
use Modelarium\GeneratedItem;
17
use Modelarium\Types\FormulariumScalarType;
18
use Symfony\Component\VarExporter\VarExporter;
19
20
class ModelGenerator extends BaseGenerator
21
{
22
    /**
23
     * @var ObjectType
24
     */
25
    protected $type = null;
26
27
    /**
28
     * @var \Nette\PhpGenerator\ClassType
0 ignored issues
show
Bug introduced by
The type Nette\PhpGenerator\ClassType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
     */
30
    protected $class = null;
31
32
    /**
33
     * fillable attributes
34
     *
35
     * @var array
36
     */
37
    protected $fillable = [];
38
39
    /**
40
     * fillable attributes
41
     *
42
     * @var array
43
     */
44
    protected $hidden = [];
45
46
    /**
47 5
     * cast attributes
48
     *
49 5
     * @var array
50 5
     */
51 5
    protected $casts = [];
52 5
53 5
    /**
54
     *
55 5
     * @var string
56 5
     */
57 5
    protected $parentClassName = '\Illuminate\Database\Eloquent\Model';
58 5
59 5
    /**
60
     * fields
61
     *
62 5
     * @var array
63
     */
64
    protected $fields = [];
65
66
    /**
67
     *
68
     * @var array
69
     */
70
    protected $traits = [];
71
72
    public function generate(): GeneratedCollection
73
    {
74
        $x = new GeneratedCollection([
75
            new GeneratedItem(
76
                GeneratedItem::TYPE_MODEL,
77
                $this->generateString(),
78
                $this->getGenerateFilename()
79
            ),
80
            new GeneratedItem(
81
                GeneratedItem::TYPE_MODEL,
82
                $this->stubToString('model'),
83
                $this->getGenerateFilename(false),
84
                true
85
            )
86
        ]);
87
        return $x;
88
    }
89
90
    protected function processField(
91
        string $typeName,
92
        \GraphQL\Type\Definition\FieldDefinition $field,
93 5
        \GraphQL\Language\AST\NodeList $directives
94
    ): void {
95
        $fieldName = $field->name;
96
97 5
        if ($typeName === 'ID') {
98 5
            return;
99
        }
100 5
101 5
        $scalarType = $this->parser->getScalarType($typeName);
102
103 1
        if ($scalarType) {
104
            if ($scalarType instanceof FormulariumScalarType) {
105
                $this->fields[$fieldName] = $scalarType->processDirectives(
106 5
                    $fieldName,
107 5
                    $directives
108
                )->toArray();
109 5
            }
110 5
        }
111 5
    }
112 5
113 4
    protected function processBasetype(
114 4
        \GraphQL\Type\Definition\FieldDefinition $field,
115
        \GraphQL\Language\AST\NodeList $directives
116 4
    ): void {
117
        $fieldName = $field->name;
118
119 4
        $isRequired = false;
120
121 5
        if ($field->type instanceof NonNull) {
122 1
            $isRequired = true;
123 1
            $type = $field->type->getWrappedType();
124
        } else {
125 1
            $type = $field->type;
126
        }
127
128 1
        if ($field->type instanceof ListOfType) {
129
            $type = $field->type->getWrappedType();
130 4
        }
131 3
132 3
        $validators = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $validators is dead and can be removed.
Loading history...
133
        if ($isRequired) {
134 3
            $validators = [
135
                Datatype::REQUIRED => true
136
            ];
137 3
        }
138 4
139 1
        foreach ($directives as $directive) {
140 1
            $name = $directive->name->value;
141 1
            switch ($name) {
142
            case 'fillableAPI':
143 1
                $this->fillable[] = $fieldName;
144
                break;
145
            case 'hiddenAPI':
146 1
                $this->hidden[] = $fieldName;
147
                break;
148 4
            case 'casts':
149
                foreach ($directive->arguments as $arg) {
150
                    /**
151
                     * @var \GraphQL\Language\AST\ArgumentNode $arg
152 5
                     */
153
154
                    $value = $arg->value->value;
0 ignored issues
show
Bug introduced by
Accessing value on the interface GraphQL\Language\AST\ValueNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
155 7
156
                    switch ($arg->name->value) {
157
                    case 'type':
158 7
                        $this->casts[$fieldName] = $value;
159 1
                    }
160
                }
161 1
                break;
162 1
            }
163 1
        }
164
165
        $typeName = $type->name; /** @phpstan-ignore-line */
166 7
        $this->processField($typeName, $field, $directives);
167
    }
168
169 7
    protected function processRelationship(
170
        \GraphQL\Type\Definition\FieldDefinition $field,
171
        \GraphQL\Language\AST\NodeList $directives
172 7
    ): void {
173
        $lowerName = mb_strtolower($this->inflector->singularize($field->name));
174 7
        $lowerNamePlural = $this->inflector->pluralize($lowerName);
175
176
        $targetClass = 'App\\' . Str::studly($this->inflector->singularize($field->name));
177 7
178
        foreach ($directives as $directive) {
179 7
            $name = $directive->name->value;
180 7
            switch ($name) {
181 7
            case 'belongsTo':
182 7
                $this->class->addMethod($lowerName)
183
                    ->setPublic()
184
                    ->setBody("return \$this->belongsTo($targetClass::class);");
185
                break;
186 5
187
            case 'belongsToMany':
188
                $this->class->addMethod($lowerNamePlural)
189
                    ->setPublic()
190
                    ->setBody("return \$this->belongsToMany($targetClass::class);");
191
                break;
192
193
            case 'hasOne':
194
                $this->class->addMethod($lowerName)
195 7
                    ->setPublic()
196 7
                    ->setBody("return \$this->hasOne($targetClass::class);");
197 7
                break;
198
199
            case 'hasMany':
200 7
                $target = $this->inflector->singularize($targetClass);
201 7
                $this->class->addMethod($lowerNamePlural)
202 7
                    ->setPublic()
203 7
                    ->setBody("return \$this->hasMany($target::class);");
204
                break;
205
            default:
206 7
                break;
207 7
            }
208 7
        }
209 7
210
        if ($field->type instanceof NonNull) {
211
            $type = $field->type->getWrappedType();
212 7
        } else {
213 7
            $type = $field->type;
214 7
        }
215 7
216
        if ($field->type instanceof ListOfType) {
217
            $type = $field->type->getWrappedType();
218 7
        }
219 7
220 7
        $typeName = $type->name; /** @phpstan-ignore-line */
221 7
        if ($typeName) {
222
            $this->processField($typeName, $field, $directives);
223
        }
224 7
    }
225 7
226 7
    protected function processDirectives(
227 7
        \GraphQL\Language\AST\NodeList $directives
228
    ): void {
229
        foreach ($directives as $directive) {
230 7
            $name = $directive->name->value;
231 7
            switch ($name) {
232
            case 'softDeletesDB':
233
                $this->traits[] = '\Illuminate\Database\Eloquent\SoftDeletes';
234 5
                break;
235
            case 'notifiable':
236 5
                $this->traits[] = '\Illuminate\Notifications\Notifiable';
237
                break;
238
            case 'mustVerifyEmail':
239
                $this->traits[] = '\Illuminate\Notifications\MustVerifyEmail';
240
                break;
241
            case 'rememberToken':
242
                $this->hidden[] = 'remember_token';
243
                break;
244
            case 'extends':
245
                foreach ($directive->arguments as $arg) {
246
                    /**
247
                     * @var \GraphQL\Language\AST\ArgumentNode $arg
248
                     */
249
250
                    $value = $arg->value->value;
0 ignored issues
show
Bug introduced by
Accessing value on the interface GraphQL\Language\AST\ValueNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
251
252
                    switch ($arg->name->value) {
253
                    case 'class':
254
                        $this->parentClassName = $value;
255
                    }
256
                }
257
            }
258
        }
259
    }
260
261
    protected function formulariumModel(
262
263
    ): string {
264
        foreach ($this->fields as $f) {
265
            $string = <<<EOF
0 ignored issues
show
Unused Code introduced by
The assignment to $string is dead and can be removed.
Loading history...
266
            new \Formularium\Field(
267
                '{$f->name}',
268
                '',
269
                [ // extensions
270
                ],
271
                [ // validators
272
                ]
273
            ),
274
EOF;
275
        }
276
        return '';
277
    }
278
279
    public function generateString(): string
280
    {
281
        $namespace = new \Nette\PhpGenerator\PhpNamespace('App');
0 ignored issues
show
Bug introduced by
The type Nette\PhpGenerator\PhpNamespace was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
282
283
        $this->class = $namespace->addClass('Base' . $this->studlyName);
284
        $this->class->setExtends($this->parentClassName)
285
            ->addComment("This file was automatically generated by Modelarium.");
286
287
        $this->processGraphql();
288
289
        foreach ($this->traits as $trait) {
290
            $this->class->addTrait($trait);
291
        }
292
293
        $this->class->addProperty('fillable')
294
            ->setProtected()
295
            ->setValue($this->fillable)
296
            ->setComment("The attributes that are mass assignable.\n@var array")
297
            ->setInitialized();
298
299
        $this->class->addProperty('hidden')
300
            ->setProtected()
301
            ->setValue($this->hidden)
302
            ->setComment("The attributes that should be hidden for arrays.\n@var array")
303
            ->setInitialized();
304
305
        $this->class->addProperty('casts')
306
            ->setProtected()
307
            ->setValue($this->casts)
308
            ->setComment("The attributes that should be cast to native types.\n@var array")
309
            ->setInitialized();
310
311
        $this->class->addMethod('getFormularium')
312
            ->setPublic()
313
            ->setStatic()
314
            ->setReturnType('\Formularium\Model')
315
            ->addComment('@return \Formularium\Model')
316
            ->addBody(
317
                '$fields = ?;' . "\n" .
318
                '$model = \Formularium\Model::create(?, $fields);' . "\n" .
319
                'return $model;',
320
                [
321
                    $this->fields,
322
                    $this->studlyName,
323
                ]
324
            );
325
326
        $this->class->addMethod('getRandomData')
327
            ->addComment('@return array')
328
            ->setPublic()
329
            ->setStatic()
330
            ->setReturnType('array')
331
            ->addBody('return static::getFormularium()->getRandom();');
332
333
        $printer = new \Nette\PhpGenerator\PsrPrinter;
0 ignored issues
show
Bug introduced by
The type Nette\PhpGenerator\PsrPrinter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
334
        return "<?php declare(strict_types=1);\n\n" . $printer->printNamespace($namespace);
335
    }
336
337
    protected function processGraphql(): void
338
    {
339
        foreach ($this->type->getFields() as $field) {
340
            $directives = $field->astNode->directives;
341
            if (
342
                ($field->type instanceof ObjectType) ||
343
                ($field->type instanceof ListOfType) ||
344
                ($field->type instanceof NonNull) && (
345
                    ($field->type->getWrappedType() instanceof ObjectType) ||
346
                    ($field->type->getWrappedType() instanceof ListOfType)
347
                )
348
            ) {
349
                // relationship
350
                $this->processRelationship($field, $directives);
351
            } else {
352
                $this->processBasetype($field, $directives);
353
            }
354
        }
355
356
        /**
357
         * @var \GraphQL\Language\AST\NodeList|null
358
         */
359
        $directives = $this->type->astNode->directives;
360
        if ($directives) {
361
            $this->processDirectives($directives);
0 ignored issues
show
Bug introduced by
$directives of type GraphQL\Language\AST\DirectiveNode[] is incompatible with the type GraphQL\Language\AST\NodeList expected by parameter $directives of Modelarium\Laravel\Targe...or::processDirectives(). ( Ignorable by Annotation )

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

361
            $this->processDirectives(/** @scrutinizer ignore-type */ $directives);
Loading history...
362
        }
363
    }
364
365
    public function getGenerateFilename(bool $base = true): string
366
    {
367
        return $this->getBasePath('app/' . ($base ? 'Base' : '') . $this->studlyName . '.php');
368
    }
369
}
370