Completed
Pull Request — master (#153)
by Alex
11:02
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
            list($thruName, $fkMethodName, $rkMethodName) = $this->getRelationsHasManyKeyNames($foo);
515
516
            $keyRaw = $foo->$fkMethodName();
517
            $keySegments = explode('.', $keyRaw);
518
            $keyName = $keySegments[count($keySegments)-1];
519
            $localRaw = $foo->$rkMethodName();
520
            $localSegments = explode('.', $localRaw);
521
            $localName = $localSegments[count($localSegments)-1];
522
            if (null !== $thruName) {
523
                $thruRaw = $foo->$thruName();
524
                $thruSegments = explode('.', $thruRaw);
525
                $thruName = $thruSegments[count($thruSegments)-1];
526
            }
527
            $first = $keyName;
528
            $last = $localName;
529
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
530
        }
531
    }
532
533
    /**
534
     * @param $rels
535
     * @param $hooks
536
     */
537
    private function getRelationshipsHasOne($rels, &$hooks)
538
    {
539
        foreach ($rels['HasOne'] as $property => $foo) {
540
            if ($foo instanceof MorphOne) {
541
                continue;
542
            }
543
            $isBelong = $foo instanceof BelongsTo;
544
            $mult = $isBelong ? '1' : '0..1';
545
            $targ = get_class($foo->getRelated());
546
547
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
548
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
549
550
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
551
            $keySegments = explode('.', $keyName);
552
            $keyName = $keySegments[count($keySegments)-1];
553
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
554
            $localSegments = explode('.', $localRaw);
555
            $localName = $localSegments[count($localSegments)-1];
556
            $first = $isBelong ? $localName : $keyName;
557
            $last = $isBelong ? $keyName : $localName;
558
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
559
        }
560
    }
561
562
    /**
563
     * @param $rels
564
     * @param $hooks
565
     */
566
    private function getRelationshipsKnownPolyMorph($rels, &$hooks)
567
    {
568
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
569
            $isMany = $foo instanceof MorphToMany;
570
            $targ = get_class($foo->getRelated());
571
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
572
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
573
574
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
575
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
576
577
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
578
            $keySegments = explode('.', $keyRaw);
579
            $keyName = $keySegments[count($keySegments)-1];
580
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
581
            $localSegments = explode('.', $localRaw);
582
            $localName = $localSegments[count($localSegments)-1];
583
            $first = $isMany ? $keyName : $localName;
584
            $last = $isMany ? $localName : $keyName;
585
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
586
        }
587
    }
588
589
    /**
590
     * @param $rels
591
     * @param $hooks
592
     */
593
    private function getRelationshipsUnknownPolyMorph($rels, &$hooks)
594
    {
595
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
596
            $isMany = $foo instanceof MorphToMany;
597
            $targ = get_class($foo->getRelated());
598
            $mult = $isMany ? '*' : '1';
599
600
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
601
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
602
603
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
604
            $keySegments = explode('.', $keyRaw);
605
            $keyName = $keySegments[count($keySegments)-1];
606
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
607
            $localSegments = explode('.', $localRaw);
608
            $localName = $localSegments[count($localSegments)-1];
609
610
            $first = $keyName;
611
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
612
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
613
        }
614
    }
615
616
    /**
617
     * SUpplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
618
     *
619
     * @return array
620
     */
621
    public function retrieveCasts()
622
    {
623
        if (method_exists($this, 'getCasts')) {
624
            return $this->getCasts();
625
        }
626
        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...
627
    }
628
629
    /**
630
     * Return list of relations to be eager-loaded by Laravel query provider.
631
     *
632
     * @return array
633
     */
634
    public function getEagerLoad()
635
    {
636
        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

636
        /** @scrutinizer ignore-call */ 
637
        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...
637
        return $this->loadEagerRelations;
638
    }
639
640
    /**
641
     * Set list of relations to be eager-loaded.
642
     *
643
     * @param array $relations
644
     */
645
    public function setEagerLoad(array $relations)
646
    {
647
        $check = array_map('strval', $relations);
648
        assert($relations == $check, 'All supplied relations must be resolvable to strings');
649
        $this->loadEagerRelations = $relations;
650
    }
651
652
    /*
653
     * Is this model the known side of at least one polymorphic relation?
654
     */
655
    public function isKnownPolymorphSide()
656
    {
657
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
658
        // you're turned around
659
        $rels = $this->getRelationshipsFromMethods();
660
        return !empty($rels['KnownPolyMorphSide']);
661
    }
662
663
    /*
664
     * Is this model on the unknown side of at least one polymorphic relation?
665
     */
666
    public function isUnknownPolymorphSide()
667
    {
668
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
669
        // you're turned around
670
        $rels = $this->getRelationshipsFromMethods();
671
        return !empty($rels['UnknownPolyMorphSide']);
672
    }
673
674
    /**
675
     * Extract entity gubbins detail for later downstream use.
676
     *
677
     * @return EntityGubbins
678
     */
679
    public function extractGubbins()
680
    {
681
        $multArray = [
682
            '*' => AssociationStubRelationType::MANY(),
683
            '1' => AssociationStubRelationType::ONE(),
684
            '0..1' => AssociationStubRelationType::NULL_ONE()
685
        ];
686
687
        $gubbins = new EntityGubbins();
688
        $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

688
        $gubbins->setName(/** @scrutinizer ignore-type */ $this->getEndpointName());
Loading history...
689
        $gubbins->setClassName(get_class($this));
690
691
        $lowerNames = [];
692
693
        $fields = $this->metadata();
694
        $entityFields = [];
695
        foreach ($fields as $name => $field) {
696
            if (in_array(strtolower($name), $lowerNames)) {
697
                $msg = 'Property names must be unique, without regard to case';
698
                throw new \Exception($msg);
699
            }
700
            $lowerNames[] = strtolower($name);
701
            $nuField = new EntityField();
702
            $nuField->setName($name);
703
            $nuField->setIsNullable($field['nullable']);
704
            $nuField->setReadOnly(false);
705
            $nuField->setCreateOnly(false);
706
            $nuField->setDefaultValue($field['default']);
707
            $nuField->setIsKeyField($this->getKeyName() == $name);
708
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
709
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
710
            $entityFields[$name] = $nuField;
711
        }
712
        $isEmpty = (0 === count($entityFields));
713
        if (!($isEmpty && $this->isRunningInArtisan())) {
714
            $gubbins->setFields($entityFields);
715
        }
716
717
        $rawRels = $this->getRelationships();
718
        $stubs = [];
719
        foreach ($rawRels as $key => $rel) {
720
            foreach ($rel as $rawName => $deets) {
721
                foreach ($deets as $relName => $relGubbins) {
722
                    if (in_array(strtolower($relName), $lowerNames)) {
723
                        $msg = 'Property names must be unique, without regard to case';
724
                        throw new \Exception($msg);
725
                    }
726
                    $lowerNames[] = strtolower($relName);
727
                    $gubbinsType = $relGubbins['type'];
728
                    $property = $relGubbins['property'];
729
                    $isPoly = isset($gubbinsType);
730
                    $targType = 'known' != $gubbinsType ? $rawName : null;
731
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
732
                    $stub->setBaseType(get_class($this));
733
                    $stub->setRelationName($property);
734
                    $stub->setKeyField($relGubbins['local']);
735
                    $stub->setForeignField($targType ? $key : null);
736
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
737
                    $stub->setTargType($targType);
738
                    if (null !== $relGubbins['through']) {
739
                        $stub->setThroughField($relGubbins['through']);
740
                    }
741
                    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

741
                    /** @scrutinizer ignore-call */ 
742
                    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...
742
                    $stubs[$property] = $stub;
743
                }
744
            }
745
        }
746
        $gubbins->setStubs($stubs);
747
748
        return $gubbins;
749
    }
750
751
    public function synthLiteralPK()
752
    {
753
        if (!$this->isKnownPolymorphSide()) {
754
            return;
755
        }
756
        $fieldName = LaravelReadQuery::PK;
757
        $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

757
        /** @scrutinizer ignore-call */ 
758
        $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...
758
    }
759
760
    public function isRunningInArtisan()
761
    {
762
        return App::runningInConsole() && !App::runningUnitTests();
763
    }
764
765
    protected function getTableColumns()
766
    {
767
        if (0 === count(self::$tableColumns)) {
768
            $table = $this->getTable();
769
            $connect = $this->getConnection();
770
            $builder = $connect->getSchemaBuilder();
771
            $columns = $builder->getColumnListing($table);
772
773
            self::$tableColumns = $columns;
774
        }
775
        return self::$tableColumns;
776
    }
777
778
    protected function getTableDoctrineColumns()
779
    {
780
        if (0 === count(self::$tableColumnsDoctrine)) {
781
            $table = $this->getTable();
782
            $connect = $this->getConnection();
783
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
784
785
            self::$tableColumnsDoctrine = $columns;
786
        }
787
        return self::$tableColumnsDoctrine;
788
    }
789
790
    public function reset()
791
    {
792
        self::$tableData = [];
793
        self::$tableColumnsDoctrine = [];
794
        self::$tableColumns = [];
795
    }
796
797
    private function getRelationsHasManyKeyNames($foo)
798
    {
799
        $thruName = null;
800
        if ($foo instanceof HasManyThrough) {
801
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
802
            $thruName = $this->polyglotThroughKeyMethodNames($foo);
803
            return array($thruName, $fkMethodName, $rkMethodName);
804
        } elseif ($foo instanceof BelongsToMany) {
805
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, true);
806
            return array($thruName, $fkMethodName, $rkMethodName);
807
        } else {
808
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
809
            return array($thruName, $fkMethodName, $rkMethodName);
810
        }
811
    }
812
}
813