ModelSchema   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 441
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 38
eloc 98
dl 0
loc 441
rs 9.36
c 2
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A fields() 0 3 1
A isIndexable() 0 3 1
A __construct() 0 15 2
A isMutable() 0 7 2
A model() 0 3 1
A getModelClass() 0 3 1
A relations() 0 3 1
A getModel() 0 3 1
A getKeyField() 0 14 2
A getInstance() 0 3 1
A getFieldByKey() 0 5 1
A getFillableRelationFields() 0 4 1
A isSearchable() 0 3 1
A getFillableFields() 0 4 1
A getLookupFields() 0 22 1
A getRelationFields() 0 12 3
A getSearchableFields() 0 4 1
A getRelations() 0 21 3
A getConnections() 0 4 2
A typename() 0 3 1
A isSortable() 0 3 1
A scopeQuery() 0 3 1
A getRegistry() 0 3 1
A getSearchableRelationFields() 0 4 1
A getLookupTypes() 0 4 1
A getFields() 0 8 2
A getSortableFields() 0 4 1
A getQuery() 0 5 1
A getTypename() 0 3 1
1
<?php
2
3
namespace Bakery\Eloquent;
4
5
use Bakery\Utils\Utils;
6
use Bakery\Fields\Field;
7
use Illuminate\Support\Str;
8
use Bakery\Fields\EloquentField;
9
use Bakery\Support\TypeRegistry;
10
use Illuminate\Support\Collection;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Database\Eloquent\Builder;
13
use Bakery\Eloquent\Concerns\Authorizable;
14
use Bakery\Eloquent\Concerns\MutatesModel;
15
16
abstract class ModelSchema
17
{
18
    use Authorizable;
19
    use MutatesModel;
20
21
    /**
22
     * @var \Bakery\Support\Schema
23
     */
24
    protected $schema;
25
26
    /**
27
     * @var \Bakery\Support\TypeRegistry
28
     */
29
    protected $registry;
30
31
    /**
32
     * @var \Illuminate\Contracts\Auth\Access\Gate
33
     */
34
    protected $gate;
35
36
    /**
37
     * @var string
38
     */
39
    protected $model;
40
41
    /**
42
     * @var Model
43
     */
44
    protected $instance;
45
46
    /**
47
     * Indicates if the model can be mutated.
48
     * Setting this to false will make sure no mutations are generated for that model.
49
     *
50
     * @var bool
51
     */
52
    protected $mutable = true;
53
54
    /**
55
     * Indicates if the model can be indexed.
56
     *
57
     * @var bool
58
     */
59
    protected $indexable = true;
60
61
    /**
62
     * ModelSchema constructor.
63
     *
64
     * @param \Bakery\Support\TypeRegistry $registry
65
     * @param \Illuminate\Database\Eloquent\Model|null $instance
66
     */
67
    public function __construct(TypeRegistry $registry, Model $instance = null)
68
    {
69
        $this->registry = $registry;
70
71
        if ($instance) {
72
            $this->instance = $instance;
73
        } else {
74
            $model = $this->model();
75
76
            Utils::invariant(
77
                is_subclass_of($model, Model::class),
78
                'Defined model on '.class_basename($this).' is not a subclass of '.Model::class
79
            );
80
81
            $this->instance = resolve($model);
0 ignored issues
show
Documentation Bug introduced by
It seems like resolve($model) can also be of type Illuminate\Contracts\Foundation\Application. However, the property $instance is declared as type Illuminate\Database\Eloquent\Model. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
82
        }
83
    }
84
85
    /**
86
     * Define the eloquent model of the model schema.
87
     *
88
     * @return string
89
     */
90
    protected function model()
91
    {
92
        return $this->model;
93
    }
94
95
    /**
96
     * Get the fields of the model.
97
     *
98
     * @return array
99
     */
100
    public function fields(): array
101
    {
102
        return [];
103
    }
104
105
    /**
106
     * Get the relation fields of the model.
107
     *
108
     * @return array
109
     */
110
    public function relations(): array
111
    {
112
        return [];
113
    }
114
115
    /**
116
     * Get the class of the model.
117
     *
118
     * @return string
119
     */
120
    public function getModelClass(): string
121
    {
122
        return $this->model;
123
    }
124
125
    /**
126
     * Get the eloquent model of the model schema.
127
     *
128
     * @return Model
129
     */
130
    public function getModel(): Model
131
    {
132
        return $this->instance;
133
    }
134
135
    /**
136
     * Returns if the schema is mutable.
137
     *
138
     * @return bool
139
     */
140
    public function isMutable()
141
    {
142
        if ($this->getFillableFields()->merge($this->getFillableRelationFields())->isEmpty()) {
143
            return false;
144
        }
145
146
        return $this->mutable;
147
    }
148
149
    /**
150
     * Determine if the model is indexable.
151
     *
152
     * @return bool
153
     */
154
    public function isIndexable()
155
    {
156
        return $this->indexable;
157
    }
158
159
    /**
160
     * Returns if the schema is sortable.
161
     */
162
    public function isSortable(): bool
163
    {
164
        return $this->getSortableFields()->isNotEmpty();
165
    }
166
167
    /**
168
     * Returns if the schema is searchable.
169
     *
170
     * @return bool
171
     */
172
    public function isSearchable()
173
    {
174
        return $this->getSearchableFields()->merge($this->getSearchableRelationFields())->isNotEmpty();
175
    }
176
177
    /**
178
     * Return the typename of the model schema.
179
     *
180
     * @return string
181
     */
182
    public function getTypename(): string
183
    {
184
        return Utils::typename($this->getModel());
185
    }
186
187
    /**
188
     * @alias getTypename()
189
     * @return string
190
     */
191
    public function typename(): string
192
    {
193
        return $this->getTypename();
194
    }
195
196
    /**
197
     * Get the key (ID) field.
198
     *
199
     * @return array
200
     */
201
    protected function getKeyField(): array
202
    {
203
        $key = $this->instance->getKeyName();
204
205
        if (! $key) {
206
            return [];
207
        }
208
209
        $field = $this->registry->field($this->registry->ID())
210
            ->accessor($key)
211
            ->fillable(false)
212
            ->unique();
213
214
        return [$key => $field];
215
    }
216
217
    /**
218
     * Get all the readable fields.
219
     *
220
     * @return \Illuminate\Support\Collection
221
     */
222
    public function getFields(): Collection
223
    {
224
        return collect($this->getKeyField())->merge($this->fields())->map(function (Field $field, string $key) {
225
            if (! $field->getAccessor()) {
226
                $field->accessor($key);
227
            }
228
229
            return $field;
230
        });
231
    }
232
233
    /**
234
     * Get the fields that can be filled.
235
     *
236
     * This excludes the ID field and other fields that are guarded from
237
     * mass assignment exceptions.
238
     *
239
     * @return \Illuminate\Support\Collection
240
     */
241
    public function getFillableFields(): Collection
242
    {
243
        return $this->getFields()->filter(function (Field $field) {
244
            return $field->isFillable();
245
        });
246
    }
247
248
    /**
249
     * Get the fields than can be sorted.
250
     */
251
    public function getSortableFields(): Collection
252
    {
253
        return $this->getFields()->filter(function (Field $field) {
254
            return $field->isSortable();
255
        });
256
    }
257
258
    /**
259
     * The fields that can be used in fulltext search.
260
     *
261
     * @return \Illuminate\Support\Collection
262
     */
263
    public function getSearchableFields(): Collection
264
    {
265
        return $this->getFields()->filter(function (Field $field) {
266
            return $field->isSearchable();
267
        });
268
    }
269
270
    /**
271
     * The relation fields that can be used in fulltext search.
272
     *
273
     * @return \Illuminate\Support\Collection
274
     */
275
    public function getSearchableRelationFields(): Collection
276
    {
277
        return $this->getRelationFields()->filter(function (Field $field) {
278
            return $field->isSearchable();
279
        });
280
    }
281
282
    /**
283
     * The fields that can be used to look up this model.
284
     *
285
     * @return \Illuminate\Support\Collection
286
     */
287
    public function getLookupFields(): Collection
288
    {
289
        $fields = collect($this->getFields())
290
            ->filter(function (Field $field) {
291
                return $field->isUnique();
292
            });
293
294
        $relations = collect($this->getRelationFields())
295
            ->filter(function ($field) {
296
                return $field instanceof EloquentField;
297
            })
298
            ->map(function (EloquentField $field) {
299
                $lookupTypeName = $field->getName().'LookupType';
300
301
                return $this->registry->field($lookupTypeName);
302
            });
303
304
        return collect()
305
            ->merge($fields)
306
            ->merge($relations)
307
            ->map(function (Field $field) {
308
                return $field->nullable();
309
            });
310
    }
311
312
    /**
313
     * Get the lookup types.
314
     *
315
     * @return \Illuminate\Support\Collection
316
     */
317
    public function getLookupTypes(): Collection
318
    {
319
        return $this->getLookupFields()->map(function (Field $field) {
320
            return $field->getType();
321
        });
322
    }
323
324
    /**
325
     * Get the relational fields.
326
     *
327
     * @return \Illuminate\Support\Collection
328
     */
329
    public function getRelationFields(): Collection
330
    {
331
        return collect($this->relations())->map(function (Field $field, string $key) {
332
            if (! $field->getAccessor()) {
333
                $field->accessor($key);
334
            }
335
336
            if (! $field->getWith()) {
337
                $field->with($field->getAccessor());
338
            }
339
340
            return $field;
341
        });
342
    }
343
344
    /**
345
     * Get the fillable relational fields.
346
     *
347
     * @return \Illuminate\Support\Collection
348
     */
349
    public function getFillableRelationFields(): Collection
350
    {
351
        return $this->getRelationFields()->filter(function (Field $field) {
352
            return $field->isFillable();
353
        });
354
    }
355
356
    /**
357
     * Get the Eloquent relations of the model.
358
     * This will only return relations that are defined in the model schema.
359
     *
360
     * @return \Illuminate\Support\Collection
361
     */
362
    public function getRelations(): Collection
363
    {
364
        return collect($this->relations())->map(function (Field $field, string $key) {
365
            if (! $field->getAccessor()) {
366
                $field->accessor($key);
367
            }
368
369
            return $field;
370
        })->map(function (Field $field) {
371
            if ($field instanceof EloquentField) {
372
                return $field->getRelation($this->getModel());
373
            }
374
375
            $accessor = $field->getAccessor();
376
377
            Utils::invariant(
378
                method_exists($this->getModel(), $accessor),
379
                'Relation "'.$accessor.'" is not defined on "'.get_class($this->getModel()).'".'
380
            );
381
382
            return $this->getModel()->{$accessor}();
383
        });
384
    }
385
386
    /**
387
     * Return a field with a defined.
388
     *
389
     * @param string $key
390
     * @return Field|null
391
     */
392
    public function getFieldByKey(string $key): ?Field
393
    {
394
        return $this->getFields()->merge($this->getRelationFields())
395
            ->first(function (Field $field, $fieldKey) use ($key) {
396
                return $fieldKey === $key;
397
            });
398
    }
399
400
    /**
401
     * Get the connections of the resource.
402
     *
403
     * @return \Illuminate\Support\Collection
404
     */
405
    public function getConnections(): Collection
406
    {
407
        return collect($this->getRelationFields())->map(function (Field $field, $key) {
408
            return $field->isList() ? Str::singular($key).'Ids' : $key.'Id';
409
        });
410
    }
411
412
    /**
413
     * Get the instance of the model schema.
414
     *
415
     * @return \Illuminate\Database\Eloquent\Model
416
     */
417
    public function getInstance(): Model
418
    {
419
        return $this->instance;
420
    }
421
422
    /**
423
     * Get the registry of the model schema.
424
     *
425
     * @return \Bakery\Support\TypeRegistry
426
     */
427
    public function getRegistry(): TypeRegistry
428
    {
429
        return $this->registry;
430
    }
431
432
    /**
433
     * Boot the query builder on the underlying model.
434
     *
435
     * @return \Illuminate\Database\Eloquent\Builder
436
     */
437
    public function getQuery(): Builder
438
    {
439
        $model = $this->getModel();
440
441
        return $this->scopeQuery($model->newQuery());
442
    }
443
444
    /**
445
     * Scope the query on the model schema. This method can be overridden to always
446
     * scope the query when executing queries/mutations on this schema.
447
     *
448
     * Note that this does not work for relations, in these cases you
449
     * should consider using Laravel's global scopes.
450
     *
451
     * @param \Illuminate\Database\Eloquent\Builder $query
452
     * @return \Illuminate\Database\Eloquent\Builder
453
     */
454
    protected function scopeQuery(Builder $query): Builder
455
    {
456
        return $query;
457
    }
458
}
459