Passed
Push — master ( 1d22e7...9503af )
by Bruno
08:21 queued 10s
created

ModelGenerator::processRelationship()   C

Complexity

Conditions 12
Paths 72

Size

Total Lines 88
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 65
c 1
b 0
f 0
dl 0
loc 88
ccs 31
cts 31
cp 1
rs 6.3369
cc 12
nc 72
nop 2
crap 12

How to fix   Long Method    Complexity   

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 Illuminate\Support\Str;
7
use GraphQL\Type\Definition\ListOfType;
8
use GraphQL\Type\Definition\NonNull;
9
use GraphQL\Type\Definition\ObjectType;
10
use Modelarium\BaseGenerator;
11
use Modelarium\GeneratedCollection;
12
use Modelarium\GeneratedItem;
13
use Modelarium\Types\FormulariumScalarType;
14
use Nette\PhpGenerator\Method;
15
16
class ModelGenerator extends BaseGenerator
17
{
18
    /**
19
     * @var string
20
     */
21
    protected $stubDir = __DIR__ . "/stubs/";
22
23
    /**
24
     * @var ObjectType
25
     */
26
    protected $type = null;
27
28
    /**
29
     * @var \Nette\PhpGenerator\ClassType
30
     */
31
    protected $class = null;
32
33
    /**
34
     * fillable attributes
35
     *
36
     * @var array
37
     */
38
    protected $fillable = [];
39
40
    /**
41
     * fillable attributes
42
     *
43
     * @var array
44
     */
45
    protected $hidden = [];
46
47 5
    /**
48
     * cast attributes
49 5
     *
50 5
     * @var array
51 5
     */
52 5
    protected $casts = [];
53 5
54
    /**
55 5
     *
56 5
     * @var string
57 5
     */
58 5
    protected $parentClassName = '\Illuminate\Database\Eloquent\Model';
59 5
60
    /**
61
     * fields
62 5
     *
63
     * @var array
64
     */
65
    protected $fields = [];
66
67
    /**
68
     *
69
     * @var array
70
     */
71
    protected $traits = [];
72
73
    /**
74
     * cast attributes
75
     *
76
     * @var Method
77
     */
78
    protected $methodRandom = null;
79
80
    public function generate(): GeneratedCollection
81
    {
82
        $x = new GeneratedCollection([
83
            new GeneratedItem(
84
                GeneratedItem::TYPE_MODEL,
85
                $this->generateString(),
86
                $this->getGenerateFilename()
87
            ),
88
            new GeneratedItem(
89
                GeneratedItem::TYPE_MODEL,
90
                $this->stubToString('model'),
91
                $this->getGenerateFilename(false),
92
                true
93 5
            )
94
        ]);
95
        return $x;
96
    }
97 5
98 5
    protected function processField(
99
        string $typeName,
100 5
        \GraphQL\Type\Definition\FieldDefinition $field,
101 5
        \GraphQL\Language\AST\NodeList $directives,
102
        bool $isRequired
103 1
    ): void {
104
        $fieldName = $field->name;
105
106 5
        if ($typeName === 'ID') {
107 5
            return;
108
        }
109 5
110 5
        $scalarType = $this->parser->getScalarType($typeName);
111 5
112 5
        if ($scalarType) {
113 4
            if ($scalarType instanceof FormulariumScalarType) {
114 4
                $field = $scalarType->processDirectives(
115
                    $fieldName,
116 4
                    $directives
117
                );
118
                
119 4
                if ($isRequired) {
120
                    $field->setValidatorOption(
121 5
                        Datatype::REQUIRED,
122 1
                        'value',
123 1
                        true
124
                    );
125 1
                }
126
                $this->fields[$fieldName] = $field->toArray();
127
            }
128 1
        }
129
    }
130 4
131 3
    protected function processBasetype(
132 3
        \GraphQL\Type\Definition\FieldDefinition $field,
133
        \GraphQL\Language\AST\NodeList $directives
134 3
    ): void {
135
        $fieldName = $field->name;
136
137 3
        $isRequired = false;
138 4
139 1
        if ($field->type instanceof NonNull) {
140 1
            $isRequired = true;
141 1
            $type = $field->type->getWrappedType();
142
        } else {
143 1
            $type = $field->type;
144
        }
145
146 1
        if ($field->type instanceof ListOfType) {
147
            $type = $field->type->getWrappedType();
148 4
        }
149
150
        foreach ($directives as $directive) {
151
            $name = $directive->name->value;
152 5
            switch ($name) {
153
            case 'modelFillable':
154
                $this->fillable[] = $fieldName;
155 7
                break;
156
            case 'modelHidden':
157
                $this->hidden[] = $fieldName;
158 7
                break;
159 1
            case 'casts':
160
                foreach ($directive->arguments as $arg) {
161 1
                    /**
162 1
                     * @var \GraphQL\Language\AST\ArgumentNode $arg
163 1
                     */
164
165
                    $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...
166 7
167
                    switch ($arg->name->value) {
168
                    case 'type':
169 7
                        $this->casts[$fieldName] = $value;
170
                    }
171
                }
172 7
                break;
173
            }
174 7
        }
175
176
        $typeName = $type->name; /** @phpstan-ignore-line */
177 7
        $this->processField($typeName, $field, $directives, $isRequired);
178
    }
179 7
180 7
    protected function processRelationship(
181 7
        \GraphQL\Type\Definition\FieldDefinition $field,
182 7
        \GraphQL\Language\AST\NodeList $directives
183
    ): void {
184
        $lowerName = mb_strtolower($this->getInflector()->singularize($field->name));
185
        $lowerNamePlural = $this->getInflector()->pluralize($lowerName);
186 5
187
        $targetClass = '\\App\\' . Str::studly($this->getInflector()->singularize($field->name));
188
189
        $isRequired = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $isRequired is dead and can be removed.
Loading history...
190
        if ($field->type instanceof NonNull) {
191
            $type = $field->type->getWrappedType();
192
            $isRequired = true;
193
        } else {
194
            $type = $field->type;
195 7
        }
196 7
197 7
        if ($field->type instanceof ListOfType) {
198
            $type = $field->type->getWrappedType();
199
            // TODO: NonNull check again?
200 7
        }
201 7
        $typeName = $type->name;
202 7
203 7
        $generateRandom = false;
204
        foreach ($directives as $directive) {
205
            $name = $directive->name->value;
206 7
            switch ($name) {
207 7
            case 'belongsTo':
208 7
                $generateRandom = true;
209 7
                $this->class->addMethod($lowerName)
210
                    ->setPublic()
211
                    ->setReturnType('BelongsTo')
212 7
                    ->setBody("return \$this->belongsTo($targetClass::class);");
213 7
                break;
214 7
215 7
            case 'belongsToMany':
216
                $generateRandom = true;
217
                $this->class->addMethod($lowerNamePlural)
218 7
                    ->setPublic()
219 7
                    ->setReturnType('BelongsTo')
220 7
                    ->setBody("return \$this->belongsToMany($targetClass::class);");
221 7
                break;
222
223
            case 'hasOne':
224 7
                $this->class->addMethod($lowerName)
225 7
                    ->setPublic()
226 7
                    ->setBody("return \$this->hasOne($targetClass::class);");
227 7
                break;
228
229
            case 'hasMany':
230 7
                $target = $this->getInflector()->singularize($targetClass);
231 7
                $this->class->addMethod($lowerNamePlural)
232
                    ->setPublic()
233
                    ->setBody("return \$this->hasMany($target::class);");
234 5
                break;
235
236 5
            case 'morphOne':
237
                $targetField = ''; // TODO
238
                $this->class->addMethod($field->name)
239
                    ->setPublic()
240
                    ->setBody("return \$this->morphMany(\\App\\$typeName::class, '$targetField');");
241
                break;
242
243
            case 'morphMany':
244
                $targetField = ''; // TODO
245
                $this->class->addMethod($field->name)
246
                    ->setPublic()
247
                    ->setBody("return \$this->morphMany(\\App\\$typeName::class, '$targetField');");
248
                break;
249
    
250
            case 'morphTo':
251
                $this->class->addMethod($field->name)
252
                    ->setPublic()
253
                    ->setBody("return \$this->morphTo();");
254
                break;
255
256
            default:
257
                break;
258
            }
259
        }
260
261
        // TODO: relationship $this->processField($typeName, $field, $directives, $isRequired);
262
263
        if ($generateRandom) {
264
            $this->methodRandom->addBody(
265
                '$data["' . $lowerName . '_id"] = function () {' . "\n" .
266
                '    return factory(' . $targetClass . '::class)->create()->id;'  . "\n" .
267
                '};'
268
            );
269
        }
270
    }
271
272
    protected function processDirectives(
273
        \GraphQL\Language\AST\NodeList $directives
274
    ): void {
275
        foreach ($directives as $directive) {
276
            $name = $directive->name->value;
277
            switch ($name) {
278
            case 'migrationSoftDeletes':
279
                $this->traits[] = '\Illuminate\Database\Eloquent\SoftDeletes';
280
                break;
281
            case 'modelNotifiable':
282
                $this->traits[] = '\Illuminate\Notifications\Notifiable';
283
                break;
284
            case 'modelMustVerifyEmail':
285
                $this->traits[] = '\Illuminate\Notifications\MustVerifyEmail';
286
                break;
287
            case 'migrationRememberToken':
288
                $this->hidden[] = 'remember_token';
289
                break;
290
            case 'extends':
291
                foreach ($directive->arguments as $arg) {
292
                    /**
293
                     * @var \GraphQL\Language\AST\ArgumentNode $arg
294
                     */
295
296
                    $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...
297
298
                    switch ($arg->name->value) {
299
                    case 'class':
300
                        $this->parentClassName = $value;
301
                    }
302
                }
303
            }
304
        }
305
    }
306
307
    protected function formulariumModel(
308
309
    ): string {
310
        foreach ($this->fields as $f) {
311
            $string = <<<EOF
0 ignored issues
show
Unused Code introduced by
The assignment to $string is dead and can be removed.
Loading history...
312
            new \Formularium\Field(
313
                '{$f->name}',
314
                '',
315
                [ // renderable
316
                ],
317
                [ // validators
318
                ]
319
            ),
320
EOF;
321
        }
322
        return '';
323
    }
324
325
    public function generateString(): string
326
    {
327
        $namespace = new \Nette\PhpGenerator\PhpNamespace('App');
328
        $namespace->addUse('\\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo');
329
330
        $this->class = $namespace->addClass('Base' . $this->studlyName);
331
        $this->class->setExtends($this->parentClassName)
332
            ->addComment("This file was automatically generated by Modelarium.");
333
334
        $this->methodRandom = new Method('getRandomData');
335
        $this->methodRandom->addBody(
336
            '$data = static::getFormularium()->getRandom();' . "\n"
337
        );
338
339
        $this->processGraphql();
340
341
        foreach ($this->traits as $trait) {
342
            $this->class->addTrait($trait);
343
        }
344
345
        $this->class->addProperty('fillable')
346
            ->setProtected()
347
            ->setValue($this->fillable)
348
            ->setComment("The attributes that are mass assignable.\n@var array")
349
            ->setInitialized();
350
351
        $this->class->addProperty('hidden')
352
            ->setProtected()
353
            ->setValue($this->hidden)
354
            ->setComment("The attributes that should be hidden for arrays.\n@var array")
355
            ->setInitialized();
356
357
        $this->class->addProperty('casts')
358
            ->setProtected()
359
            ->setValue($this->casts)
360
            ->setComment("The attributes that should be cast to native types.\n@var array")
361
            ->setInitialized();
362
363
        $this->class->addMethod('getFields')
364
            ->setPublic()
365
            ->setStatic()
366
            ->setReturnType('array')
367
            ->addComment('@return array')
368
            ->addBody(
369
                "return ?;\n",
370
                [
371
                    $this->fields
372
                ]
373
            );
374
375
        $this->class->addMethod('getFormularium')
376
            ->setPublic()
377
            ->setStatic()
378
            ->setReturnType('\Formularium\Model')
379
            ->addComment('@return \Formularium\Model')
380
            ->addBody(
381
                '$model = \Formularium\Model::create(?, static::getFields());' . "\n" .
382
                'return $model;',
383
                [
384
                    $this->studlyName,
385
                ]
386
            );
387
        
388
        $this->methodRandom
389
            ->addComment('@return array')
390
            ->setPublic()
391
            ->setStatic()
392
            ->setReturnType('array')
393
            ->addBody('return $data;');
394
        $this->class->addMember($this->methodRandom);
395
396
        $printer = new \Nette\PhpGenerator\PsrPrinter;
397
        return "<?php declare(strict_types=1);\n\n" . $printer->printNamespace($namespace);
398
    }
399
400
    protected function processGraphql(): void
401
    {
402
        foreach ($this->type->getFields() as $field) {
403
            $directives = $field->astNode->directives;
404
            if (
405
                ($field->type instanceof ObjectType) ||
406
                ($field->type instanceof ListOfType) ||
407
                ($field->type instanceof NonNull) && (
408
                    ($field->type->getWrappedType() instanceof ObjectType) ||
409
                    ($field->type->getWrappedType() instanceof ListOfType)
410
                )
411
            ) {
412
                // relationship
413
                $this->processRelationship($field, $directives);
414
            } else {
415
                $this->processBasetype($field, $directives);
416
            }
417
        }
418
419
        /**
420
         * @var \GraphQL\Language\AST\NodeList|null
421
         */
422
        $directives = $this->type->astNode->directives;
423
        if ($directives) {
424
            $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

424
            $this->processDirectives(/** @scrutinizer ignore-type */ $directives);
Loading history...
425
        }
426
    }
427
428
    public function getGenerateFilename(bool $base = true): string
429
    {
430
        return $this->getBasePath('app/' . ($base ? 'Base' : '') . $this->studlyName . '.php');
431
    }
432
}
433