Passed
Pull Request — master (#153)
by Alex
07:18
created

MetadataTrait::addRelationsHook()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
ccs 0
cts 0
cp 0
rs 9.4285
cc 3
eloc 10
nc 4
nop 8
crap 12

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace AlgoWeb\PODataLaravel\Models;
3
4
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\Associations\AssociationStubMonomorphic;
5
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\Associations\AssociationStubPolymorphic;
6
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\Associations\AssociationStubRelationType;
7
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityField;
8
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityFieldPrimitiveType;
9
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityFieldType;
10
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityGubbins;
11
use AlgoWeb\PODataLaravel\Query\LaravelReadQuery;
12
use Illuminate\Database\Eloquent\Model;
13
use Illuminate\Database\Eloquent\Relations\BelongsTo;
14
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
15
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
16
use Illuminate\Database\Eloquent\Relations\MorphMany;
17
use Illuminate\Database\Eloquent\Relations\MorphOne;
18
use Illuminate\Database\Eloquent\Relations\MorphToMany;
19
use Illuminate\Database\Eloquent\Relations\Relation;
20
use Illuminate\Support\Facades\App;
21
22
trait MetadataTrait
23
{
24
    protected static $relationHooks = [];
25
    protected static $relationCategories = [];
26
    protected static $methodPrimary = [];
27
    protected static $methodAlternate = [];
28 3
    protected $loadEagerRelations = [];
29
    protected static $tableColumns = [];
30 3
    protected static $tableColumnsDoctrine = [];
31
    protected static $tableData = [];
32
    protected static $dontCastTypes = ['object', 'array', 'collection', 'int'];
33 2
34 2
    /*
35
     * Retrieve and assemble this model's metadata for OData packaging
36 2
     */
37
    public function metadata()
38 2
    {
39 1
        assert($this instanceof Model, get_class($this));
40
41
        // Break these out separately to enable separate reuse
42 1
        $connect = $this->getConnection();
43 1
        $builder = $connect->getSchemaBuilder();
44 1
45
        $table = $this->getTable();
46 1
47
        if (!$builder->hasTable($table)) {
48 1
            return [];
49 1
        }
50 1
        if (0 !== count(self::$tableData)) {
51
            return self::$tableData;
52 1
        }
53 1
54 1
        $columns = $this->getTableColumns();
55
        $mask = $this->metadataMask();
56 1
        $columns = array_intersect($columns, $mask);
57
58 1
        $tableData = [];
59 1
60 1
        $rawFoo = $this->getTableDoctrineColumns();
61 1
        $foo = [];
62 1
        $getters = $this->collectGetters();
63 1
        $getters = array_intersect($getters, $mask);
64 1
        $casts = $this->retrieveCasts();
65
66 1
        foreach ($rawFoo as $key => $val) {
67
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
68
            $key = trim($key, '`');
69
            $foo[$key] = $val;
70
        }
71
72
        foreach ($columns as $column) {
73 4
            // Doctrine schema manager returns columns with lowercased names
74
            $rawColumn = $foo[strtolower($column)];
75 4
            $nullable = !($rawColumn->getNotNull());
76
            $fillable = in_array($column, $this->getFillable());
77 4
            $rawType = $rawColumn->getType();
78 4
            $type = $rawType->getName();
79 4
            $default = $this->$column;
80 2
            $tableData[$column] = ['type' => $type,
81 4
                'nullable' => $nullable,
82 1
                'fillable' => $fillable,
83 1
                'default' => $default
84
            ];
85 4
        }
86
87
        foreach ($getters as $get) {
88
            if (isset($tableData[$get])) {
89
                continue;
90
            }
91 5
            $default = $this->$get;
92
            $tableData[$get] = ['type' => 'text', 'nullable' => true, 'fillable' => false, 'default' => $default];
93 5
        }
94 5
95 1
        // now, after everything's gathered up, apply Eloquent model's $cast array
96
        foreach ($casts as $key => $type) {
97
            $type = strtolower($type);
98 4
            if (array_key_exists($key, $tableData) && !in_array($type, self::$dontCastTypes)) {
99
                $tableData[$key]['type'] = $type;
100 4
            }
101
        }
102 4
103 4
        self::$tableData = $tableData;
104 4
        return $tableData;
105 4
    }
106 4
107 4
    /*
108
     * Return the set of fields that are permitted to be in metadata
109 4
     * - following same visible-trumps-hidden guideline as Laravel
110 1
     */
111 1
    public function metadataMask()
112 1
    {
113 1
        $attribs = array_keys($this->getAllAttributes());
114
115 4
        $visible = $this->getVisible();
116 4
        $hidden = $this->getHidden();
117
        if (0 < count($visible)) {
118 4
            assert(!empty($visible));
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

118
            /** @scrutinizer ignore-call */ 
119
            assert(!empty($visible));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
119
            $attribs = array_intersect($visible, $attribs);
120
        } elseif (0 < count($hidden)) {
121
            assert(!empty($hidden));
122
            $attribs = array_diff($attribs, $hidden);
123
        }
124
125
        return $attribs;
126
    }
127
128
    /*
129
     * Get the endpoint name being exposed
130
     *
131
     */
132
    public function getEndpointName()
133
    {
134
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
1 ignored issue
show
Bug Best Practice introduced by
The property endpoint does not exist on AlgoWeb\PODataLaravel\Models\MetadataTrait. Did you maybe forget to declare it?
Loading history...
135
136
        if (!isset($endpoint)) {
137
            $bitter = get_class();
138
            $name = substr($bitter, strrpos($bitter, '\\')+1);
139
            return ($name);
140
        }
141
        return ($endpoint);
142
    }
143
144
    /**
145
     * Get model's relationships.
146
     *
147
     * @return array
148
     */
149
    public function getRelationships()
150
    {
151
        if (empty(static::$relationHooks)) {
152
            $hooks = [];
153
154
            $rels = $this->getRelationshipsFromMethods(true);
155
156
            $this->getRelationshipsUnknownPolyMorph($rels, $hooks);
157
158
            $this->getRelationshipsKnownPolyMorph($rels, $hooks);
159
160
            $this->getRelationshipsHasOne($rels, $hooks);
161
162
            $this->getRelationshipsHasMany($rels, $hooks);
163
164
            static::$relationHooks = $hooks;
165
        }
166
167
        return static::$relationHooks;
168
    }
169
170
    protected function getAllAttributes()
171
    {
172
        // Adapted from http://stackoverflow.com/a/33514981
173
        // $columns = $this->getFillable();
174
        // Another option is to get all columns for the table like so:
175 3
        $columns = $this->getTableColumns();
176
        // but it's safer to just get the fillable fields
177 3
178
        $attributes = $this->getAttributes();
179 3
180 3
        foreach ($columns as $column) {
181 3
            if (!array_key_exists($column, $attributes)) {
182 3
                $attributes[$column] = null;
183 3
            }
184 3
        }
185 3
186 3
        $methods = $this->collectGetters();
187 3
188 3
        foreach ($methods as $method) {
189
            $attributes[$method] = null;
190 3
        }
191
192 3
        return $attributes;
193 3
    }
194 3
195 3
    /**
196 3
     * @param bool $biDir
197 3
     *
198 3
     * @return array
199 3
     */
200 3
    protected function getRelationshipsFromMethods($biDir = false)
201 3
    {
202
        $biDirVal = intval($biDir);
203 3
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
204 3
        if (!$isCached) {
205 3
            $model = $this;
206 3
            $relationships = [
207 3
                'HasOne' => [],
208 3
                'UnknownPolyMorphSide' => [],
209 3
                'HasMany' => [],
210 3
                'KnownPolyMorphSide' => []
211
            ];
212 3
            $methods = get_class_methods($model);
213 3
            if (!empty($methods)) {
214 3
                foreach ($methods as $method) {
215
                    if (!method_exists('Illuminate\Database\Eloquent\Model', $method)
216 3
                        && !method_exists(Mock::class, $method)
0 ignored issues
show
Bug introduced by
The type AlgoWeb\PODataLaravel\Models\Mock 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...
217 3
                        && !method_exists(MetadataTrait::class, $method)
218 3
                    ) {
219 3
                        //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
220 3
                        $reflection = new \ReflectionMethod($model, $method);
221
                        $fileName = $reflection->getFileName();
222 1
223 3
                        $file = new \SplFileObject($fileName);
224
                        $file->seek($reflection->getStartLine()-1);
225 1
                        $code = '';
226
                        while ($file->key() < $reflection->getEndLine()) {
227 1
                            $code .= $file->current();
228
                            $file->next();
229 1
                        }
230
231 3
                        $code = trim(preg_replace('/\s\s+/', '', $code));
232 2
                        assert(
233 2
                            false !== stripos($code, 'function'),
234 3
                            'Function definition must have keyword \'function\''
235 3
                        );
236 3
                        $begin = strpos($code, 'function(');
237 3
                        $code = substr($code, $begin, strrpos($code, '}')-$begin+1);
0 ignored issues
show
Bug introduced by
It seems like $begin can also be of type false; however, parameter $start of substr() does only seem to accept integer, 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

237
                        $code = substr($code, /** @scrutinizer ignore-type */ $begin, strrpos($code, '}')-$begin+1);
Loading history...
238 3
                        $lastCode = $code[strlen($code)-1];
0 ignored issues
show
Bug introduced by
It seems like $code can also be of type false; however, parameter $string of strlen() 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

238
                        $lastCode = $code[strlen(/** @scrutinizer ignore-type */ $code)-1];
Loading history...
239 3
                        assert('}' == $lastCode, 'Final character of function definition must be closing brace');
240 3
                        foreach ([
241
                                     'hasMany',
242
                                     'hasManyThrough',
243
                                     'belongsToMany',
244
                                     'hasOne',
245
                                     'belongsTo',
246
                                     'morphOne',
247
                                     'morphTo',
248
                                     'morphMany',
249
                                     'morphToMany',
250
                                     'morphedByMany'
251
                                 ] as $relation) {
252
                            $search = '$this->' . $relation . '(';
253
                            if ($pos = stripos($code, $search)) {
0 ignored issues
show
Bug introduced by
It seems like $code can also be of type false; however, parameter $haystack of stripos() 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

253
                            if ($pos = stripos(/** @scrutinizer ignore-type */ $code, $search)) {
Loading history...
Unused Code introduced by
The assignment to $pos is dead and can be removed.
Loading history...
254
                                //Resolve the relation's model to a Relation object.
255
                                $relationObj = $model->$method();
256
                                if ($relationObj instanceof Relation) {
257
                                    $relObject = $relationObj->getRelated();
258
                                    $relatedModel = '\\' . get_class($relObject);
259
                                    if (in_array(MetadataTrait::class, class_uses($relatedModel))) {
260
                                        $relations = [
261
                                            'hasManyThrough',
262
                                            'belongsToMany',
263
                                            'hasMany',
264
                                            'morphMany',
265
                                            'morphToMany',
266
                                            'morphedByMany'
267
                                        ];
268
                                        if (in_array($relation, $relations)) {
269
                                            //Collection or array of models (because Collection is Arrayable)
270
                                            $relationships['HasMany'][$method] = $biDir ? $relationObj : $relatedModel;
271
                                        } elseif ('morphTo' === $relation) {
272
                                            // Model isn't specified because relation is polymorphic
273
                                            $relationships['UnknownPolyMorphSide'][$method] =
274
                                                $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
275
                                        } else {
276
                                            //Single model is returned
277
                                            $relationships['HasOne'][$method] = $biDir ? $relationObj : $relatedModel;
278
                                        }
279
                                        if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
280
                                            $relationships['KnownPolyMorphSide'][$method] =
281
                                                $biDir ? $relationObj : $relatedModel;
282
                                        }
283
                                        if (in_array($relation, ['morphedByMany'])) {
284
                                            $relationships['UnknownPolyMorphSide'][$method] =
285
                                                $biDir ? $relationObj : $relatedModel;
286
                                        }
287
                                    }
288
                                }
289
                            }
290
                        }
291
                    }
292
                }
293
            }
294
            static::$relationCategories[$biDirVal] = $relationships;
295
        }
296
        return static::$relationCategories[$biDirVal];
297
    }
298
299
    /**
300
     * Get the visible attributes for the model.
301
     *
302
     * @return array
303
     */
304
    abstract public function getVisible();
305
306
    /**
307
     * Get the hidden attributes for the model.
308
     *
309
     * @return array
310
     */
311
    abstract public function getHidden();
312
313
    /**
314
     * Get the primary key for the model.
315
     *
316
     * @return string
317
     */
318
    abstract public function getKeyName();
319
320
    /**
321
     * Get the current connection name for the model.
322
     *
323
     * @return string
324
     */
325
    abstract public function getConnectionName();
326
327
    /**
328
     * Get the database connection for the model.
329
     *
330
     * @return \Illuminate\Database\Connection
331
     */
332
    abstract public function getConnection();
333
334
    /**
335
     * Get all of the current attributes on the model.
336
     *
337
     * @return array
338
     */
339
    abstract public function getAttributes();
340
341
    /**
342
     * Get the table associated with the model.
343
     *
344
     * @return string
345
     */
346
    abstract public function getTable();
347
348
    /**
349
     * Get the fillable attributes for the model.
350
     *
351
     * @return array
352
     */
353
    abstract public function getFillable();
354
355
    /**
356
     * Dig up all defined getters on the model.
357
     *
358
     * @return array
359
     */
360
    protected function collectGetters()
361
    {
362
        $getterz = [];
363
        $methods = get_class_methods($this);
364
        foreach ($methods as $method) {
365
            if (12 < strlen($method) && 'get' == substr($method, 0, 3)) {
366
                if ('Attribute' == substr($method, -9)) {
367
                    $getterz[] = $method;
368
                }
369
            }
370
        }
371
        $methods = [];
372
373
        foreach ($getterz as $getter) {
374
            $residual = substr($getter, 3);
375
            $residual = substr($residual, 0, -9);
0 ignored issues
show
Bug introduced by
It seems like $residual can also be of type false; however, parameter $string of substr() 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

375
            $residual = substr(/** @scrutinizer ignore-type */ $residual, 0, -9);
Loading history...
376
            $methods[] = $residual;
377
        }
378
        return $methods;
379
    }
380
381
    /**
382
     * @param       $foo
383
     * @param mixed $condition
384
     *
385
     * @return array
386
     */
387
    private function polyglotKeyMethodNames($foo, $condition = false)
388
    {
389
        $fkList = ['getQualifiedForeignKeyName', 'getForeignKey'];
390
        $rkList = ['getQualifiedRelatedKeyName', 'getOtherKey', 'getOwnerKey'];
391
392
        $fkMethodName = null;
393
        $rkMethodName = null;
394
        if ($condition) {
395
            if (array_key_exists(get_class($foo), static::$methodPrimary)) {
396
                $line = static::$methodPrimary[get_class($foo)];
397
                $fkMethodName = $line['fk'];
398
                $rkMethodName = $line['rk'];
399
            } else {
400
                $methodList = get_class_methods(get_class($foo));
401
                $fkMethodName = 'getQualifiedForeignPivotKeyName';
402
                foreach ($fkList as $option) {
403
                    if (in_array($option, $methodList)) {
404
                        $fkMethodName = $option;
405
                        break;
406
                    }
407
                }
408
                assert(
409
                    in_array($fkMethodName, $methodList),
410
                    'Selected method, ' . $fkMethodName . ', not in method list'
411
                );
412
                $rkMethodName = 'getQualifiedRelatedPivotKeyName';
413
                foreach ($rkList as $option) {
414
                    if (in_array($option, $methodList)) {
415
                        $rkMethodName = $option;
416
                        break;
417
                    }
418
                }
419
                assert(
420
                    in_array($rkMethodName, $methodList),
421
                    'Selected method, ' . $rkMethodName . ', not in method list'
422
                );
423
                $line = ['fk' => $fkMethodName, 'rk' => $rkMethodName];
424
                static::$methodPrimary[get_class($foo)] = $line;
425
            }
426
        }
427
        return [$fkMethodName, $rkMethodName];
428
    }
429
430
    private function polyglotKeyMethodBackupNames($foo, $condition = false)
431
    {
432
        $fkList = ['getForeignKey', 'getForeignKeyName', 'getQualifiedFarKeyName'];
433
        $rkList = ['getOtherKey', 'getQualifiedParentKeyName'];
434
435
        $fkMethodName = null;
436
        $rkMethodName = null;
437
        if ($condition) {
438
            if (array_key_exists(get_class($foo), static::$methodAlternate)) {
439
                $line = static::$methodAlternate[get_class($foo)];
440
                $fkMethodName = $line['fk'];
441
                $rkMethodName = $line['rk'];
442
            } else {
443
                $methodList = get_class_methods(get_class($foo));
444
                $fkCombo = array_values(array_intersect($fkList, $methodList));
445
                assert(1 <= count($fkCombo), 'Expected at least 1 element in foreign-key list, got ' . count($fkCombo));
446
                $fkMethodName = $fkCombo[0];
447
                assert(
448
                    in_array($fkMethodName, $methodList),
449
                    'Selected method, ' . $fkMethodName . ', not in method list'
450
                );
451
                $rkCombo = array_values(array_intersect($rkList, $methodList));
452
                assert(1 <= count($rkCombo), 'Expected at least 1 element in related-key list, got ' . count($rkCombo));
453
                $rkMethodName = $rkCombo[0];
454
                assert(
455
                    in_array($rkMethodName, $methodList),
456
                    'Selected method, ' . $rkMethodName . ', not in method list'
457
                );
458
                $line = ['fk' => $fkMethodName, 'rk' => $rkMethodName];
459
                static::$methodAlternate[get_class($foo)] = $line;
460
            }
461
        }
462
        return [$fkMethodName, $rkMethodName];
463
    }
464
465
    private function polyglotThroughKeyMethodNames(HasManyThrough $foo)
466
    {
467
        $thruList = ['getThroughKey', 'getQualifiedFirstKeyName'];
468
469
        $methodList = get_class_methods(get_class($foo));
470
        $thruCombo = array_values(array_intersect($thruList, $methodList));
471
        return $thruCombo[0];
472
    }
473
474
475
    /**
476
     * @param             $hooks
477
     * @param             $first
478
     * @param             $property
479
     * @param             $last
480
     * @param             $mult
481
     * @param             $targ
482
     * @param string|null $targ
483
     * @param null|mixed  $type
484
     */
485
    private function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ, $type = null, $through = null)
486
    {
487
        if (!isset($hooks[$first])) {
488
            $hooks[$first] = [];
489
        }
490
        if (!isset($hooks[$first][$targ])) {
491
            $hooks[$first][$targ] = [];
492
        }
493
        $hooks[$first][$targ][$property] = [
494
            'property' => $property,
495
            'local' => $last,
496
            'through' => $through,
497
            'multiplicity' => $mult,
498
            'type' => $type
499
        ];
500
    }
501
502
    /**
503
     * @param $rels
504
     * @param $hooks
505
     */
506
    private function getRelationshipsHasMany($rels, &$hooks)
507
    {
508
        foreach ($rels['HasMany'] as $property => $foo) {
509
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
510
                continue;
511
            }
512
            $mult = '*';
513
            $targ = get_class($foo->getRelated());
514
            $thruName = null;
515
            if ($foo instanceof HasManyThrough) {
516
                $isBelong = false;
517
                list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, true);
518
                $thruName = $this->polyglotThroughKeyMethodNames($foo);
519
            } elseif ($foo instanceof BelongsToMany) {
520
                $isBelong = true;
521
                list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
522
            } else {
523
                $isBelong = false;
524
                list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
525
            }
526
527
            $keyRaw = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fkMethodAlternate does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $fkMethodName does not seem to be defined for all execution paths leading up to this point.
Loading history...
528
            $keySegments = explode('.', $keyRaw);
529
            $keyName = $keySegments[count($keySegments)-1];
530
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rkMethodAlternate does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $rkMethodName does not seem to be defined for all execution paths leading up to this point.
Loading history...
531
            $localSegments = explode('.', $localRaw);
532
            $localName = $localSegments[count($localSegments)-1];
533
            if (null !== $thruName) {
534
                $thruRaw = $foo->$thruName();
535
                $thruSegments = explode('.', $thruRaw);
536
                $thruName = $thruSegments[count($thruSegments)-1];
537
            }
538
            $first = $keyName;
539
            $last = $localName;
540
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
541
        }
542
    }
543
544
    /**
545
     * @param $rels
546
     * @param $hooks
547
     */
548
    private function getRelationshipsHasOne($rels, &$hooks)
549
    {
550
        foreach ($rels['HasOne'] as $property => $foo) {
551
            if ($foo instanceof MorphOne) {
552
                continue;
553
            }
554
            $isBelong = $foo instanceof BelongsTo;
555
            $mult = $isBelong ? '1' : '0..1';
556
            $targ = get_class($foo->getRelated());
557
558
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
559
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
560
561
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
562
            $keySegments = explode('.', $keyName);
563
            $keyName = $keySegments[count($keySegments)-1];
564
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
565
            $localSegments = explode('.', $localRaw);
566
            $localName = $localSegments[count($localSegments)-1];
567
            $first = $isBelong ? $localName : $keyName;
568
            $last = $isBelong ? $keyName : $localName;
569
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
570
        }
571
    }
572
573
    /**
574
     * @param $rels
575
     * @param $hooks
576
     */
577
    private function getRelationshipsKnownPolyMorph($rels, &$hooks)
578
    {
579
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
580
            $isMany = $foo instanceof MorphToMany;
581
            $targ = get_class($foo->getRelated());
582
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
583
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
584
585
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
586
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
587
588
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
589
            $keySegments = explode('.', $keyRaw);
590
            $keyName = $keySegments[count($keySegments)-1];
591
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
592
            $localSegments = explode('.', $localRaw);
593
            $localName = $localSegments[count($localSegments)-1];
594
            $first = $isMany ? $keyName : $localName;
595
            $last = $isMany ? $localName : $keyName;
596
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
597
        }
598
    }
599
600
    /**
601
     * @param $rels
602
     * @param $hooks
603
     */
604
    private function getRelationshipsUnknownPolyMorph($rels, &$hooks)
605
    {
606
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
607
            $isMany = $foo instanceof MorphToMany;
608
            $targ = get_class($foo->getRelated());
609
            $mult = $isMany ? '*' : '1';
610
611
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
612
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
613
614
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
615
            $keySegments = explode('.', $keyRaw);
616
            $keyName = $keySegments[count($keySegments)-1];
617
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
618
            $localSegments = explode('.', $localRaw);
619
            $localName = $localSegments[count($localSegments)-1];
620
621
            $first = $keyName;
622
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
623
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
624
        }
625
    }
626
627
    /**
628
     * SUpplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
629
     *
630
     * @return array
631
     */
632
    public function retrieveCasts()
633
    {
634
        if (method_exists($this, 'getCasts')) {
635
            return $this->getCasts();
636
        }
637
        return $this->casts;
1 ignored issue
show
Bug Best Practice introduced by
The property casts does not exist on AlgoWeb\PODataLaravel\Models\MetadataTrait. Did you maybe forget to declare it?
Loading history...
638
    }
639
640
    /**
641
     * Return list of relations to be eager-loaded by Laravel query provider.
642
     *
643
     * @return array
644
     */
645
    public function getEagerLoad()
646
    {
647
        assert(is_array($this->loadEagerRelations));
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

647
        /** @scrutinizer ignore-call */ 
648
        assert(is_array($this->loadEagerRelations));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
648
        return $this->loadEagerRelations;
649
    }
650
651
    /**
652
     * Set list of relations to be eager-loaded.
653
     *
654
     * @param array $relations
655
     */
656
    public function setEagerLoad(array $relations)
657
    {
658
        $check = array_map('strval', $relations);
659
        assert($relations == $check, 'All supplied relations must be resolvable to strings');
660
        $this->loadEagerRelations = $relations;
661
    }
662
663
    /*
664
     * Is this model the known side of at least one polymorphic relation?
665
     */
666
    public function isKnownPolymorphSide()
667
    {
668
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
669
        // you're turned around
670
        $rels = $this->getRelationshipsFromMethods();
671
        return !empty($rels['KnownPolyMorphSide']);
672
    }
673
674
    /*
675
     * Is this model on the unknown side of at least one polymorphic relation?
676
     */
677
    public function isUnknownPolymorphSide()
678
    {
679
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
680
        // you're turned around
681
        $rels = $this->getRelationshipsFromMethods();
682
        return !empty($rels['UnknownPolyMorphSide']);
683
    }
684
685
    /**
686
     * Extract entity gubbins detail for later downstream use.
687
     *
688
     * @return EntityGubbins
689
     */
690
    public function extractGubbins()
691
    {
692
        $multArray = [
693
            '*' => AssociationStubRelationType::MANY(),
694
            '1' => AssociationStubRelationType::ONE(),
695
            '0..1' => AssociationStubRelationType::NULL_ONE()
696
        ];
697
698
        $gubbins = new EntityGubbins();
699
        $gubbins->setName($this->getEndpointName());
0 ignored issues
show
Bug introduced by
It seems like $this->getEndpointName() can also be of type false; however, parameter $name of AlgoWeb\PODataLaravel\Mo...ntityGubbins::setName() 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

699
        $gubbins->setName(/** @scrutinizer ignore-type */ $this->getEndpointName());
Loading history...
700
        $gubbins->setClassName(get_class($this));
701
702
        $lowerNames = [];
703
704
        $fields = $this->metadata();
705
        $entityFields = [];
706
        foreach ($fields as $name => $field) {
707
            if (in_array(strtolower($name), $lowerNames)) {
708
                $msg = 'Property names must be unique, without regard to case';
709
                throw new \Exception($msg);
710
            }
711
            $lowerNames[] = strtolower($name);
712
            $nuField = new EntityField();
713
            $nuField->setName($name);
714
            $nuField->setIsNullable($field['nullable']);
715
            $nuField->setReadOnly(false);
716
            $nuField->setCreateOnly(false);
717
            $nuField->setDefaultValue($field['default']);
718
            $nuField->setIsKeyField($this->getKeyName() == $name);
719
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
720
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
721
            $entityFields[$name] = $nuField;
722
        }
723
        $isEmpty = (0 === count($entityFields));
724
        if (!($isEmpty && $this->isRunningInArtisan())) {
725
            $gubbins->setFields($entityFields);
726
        }
727
728
        $rawRels = $this->getRelationships();
729
        $stubs = [];
730
        foreach ($rawRels as $key => $rel) {
731
            foreach ($rel as $rawName => $deets) {
732
                foreach ($deets as $relName => $relGubbins) {
733
                    if (in_array(strtolower($relName), $lowerNames)) {
734
                        $msg = 'Property names must be unique, without regard to case';
735
                        throw new \Exception($msg);
736
                    }
737
                    $lowerNames[] = strtolower($relName);
738
                    $gubbinsType = $relGubbins['type'];
739
                    $property = $relGubbins['property'];
740
                    $isPoly = isset($gubbinsType);
741
                    $targType = 'known' != $gubbinsType ? $rawName : null;
742
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
743
                    $stub->setBaseType(get_class($this));
744
                    $stub->setRelationName($property);
745
                    $stub->setKeyField($relGubbins['local']);
746
                    $stub->setForeignField($targType ? $key : null);
747
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
748
                    $stub->setTargType($targType);
749
                    if (null !== $relGubbins['through']) {
750
                        $stub->setThroughField($relGubbins['through']);
751
                    }
752
                    assert($stub->isOk());
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

752
                    /** @scrutinizer ignore-call */ 
753
                    assert($stub->isOk());

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
753
                    $stubs[$property] = $stub;
754
                }
755
            }
756
        }
757
        $gubbins->setStubs($stubs);
758
759
        return $gubbins;
760
    }
761
762
    public function synthLiteralPK()
763
    {
764
        if (!$this->isKnownPolymorphSide()) {
765
            return;
766
        }
767
        $fieldName = LaravelReadQuery::PK;
768
        $this->$fieldName = $this->getKey();
0 ignored issues
show
Bug introduced by
The method getKey() does not exist on AlgoWeb\PODataLaravel\Models\MetadataTrait. Did you maybe mean getKeyName()? ( Ignorable by Annotation )

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

768
        /** @scrutinizer ignore-call */ 
769
        $this->$fieldName = $this->getKey();

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...
769
    }
770
771
    public function isRunningInArtisan()
772
    {
773
        return App::runningInConsole() && !App::runningUnitTests();
774
    }
775
776
    protected function getTableColumns()
777
    {
778
        if (0 === count(self::$tableColumns)) {
779
            $table = $this->getTable();
780
            $connect = $this->getConnection();
781
            $builder = $connect->getSchemaBuilder();
782
            $columns = $builder->getColumnListing($table);
783
784
            self::$tableColumns = $columns;
785
        }
786
        return self::$tableColumns;
787
    }
788
789
    protected function getTableDoctrineColumns()
790
    {
791
        if (0 === count(self::$tableColumnsDoctrine)) {
792
            $table = $this->getTable();
793
            $connect = $this->getConnection();
794
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
795
796
            self::$tableColumnsDoctrine = $columns;
797
        }
798
        return self::$tableColumnsDoctrine;
799
    }
800
801
    public function reset()
802
    {
803
        self::$tableData = [];
804
        self::$tableColumnsDoctrine = [];
805
        self::$tableColumns = [];
806
    }
807
}
808