Completed
Pull Request — master (#153)
by Alex
02:32
created

MetadataTrait::polyglotThroughKeyMethodNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 1
crap 2
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()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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));
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()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
133
    {
134
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
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)
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);
238 3
                        $lastCode = $code[strlen($code)-1];
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)) {
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);
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
        $thruName = null;
0 ignored issues
show
Unused Code introduced by
$thruName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
470
        $methodList = get_class_methods(get_class($foo));
471
        $thruCombo = array_values(array_intersect($thruList, $methodList));
472
        return $thruCombo[0];
473
    }
474
475
476
    /**
477
     * @param             $hooks
478
     * @param             $first
479
     * @param             $property
480
     * @param             $last
481
     * @param             $mult
482
     * @param             $targ
483
     * @param string|null $targ
484
     * @param null|mixed  $type
485
     */
486
    private function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ, $type = null, $through = null)
487
    {
488
        if (!isset($hooks[$first])) {
489
            $hooks[$first] = [];
490
        }
491
        if (!isset($hooks[$first][$targ])) {
492
            $hooks[$first][$targ] = [];
493
        }
494
        $hooks[$first][$targ][$property] = [
495
            'property' => $property,
496
            'local' => $last,
497
            'through' => $through,
498
            'multiplicity' => $mult,
499
            'type' => $type
500
        ];
501
    }
502
503
    /**
504
     * @param $rels
505
     * @param $hooks
506
     */
507
    private function getRelationshipsHasMany($rels, &$hooks)
508
    {
509
        foreach ($rels['HasMany'] as $property => $foo) {
510
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
511
                continue;
512
            }
513
            $mult = '*';
514
            $targ = get_class($foo->getRelated());
515
            if ($foo instanceof HasManyThrough) {
516
                $isBelong = false;
517
                list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, true);
518
                $thruName = $this->polyglotThroughKeyMethodNames($foo);
519
            } else {
520
                $isBelong = $foo instanceof BelongsToMany;
521
                list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
522
                list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
523
                $thruName = null;
524
            }
525
526
            $keyRaw = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
0 ignored issues
show
Bug introduced by
The variable $fkMethodName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
527
            $keySegments = explode('.', $keyRaw);
528
            $keyName = $keySegments[count($keySegments)-1];
529
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
0 ignored issues
show
Bug introduced by
The variable $rkMethodName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
530
            $localSegments = explode('.', $localRaw);
531
            $localName = $localSegments[count($localSegments)-1];
532
            if (null !== $thruName) {
533
                $thruRaw = $foo->$thruName();
534
                $thruSegments = explode('.', $thruRaw);
535
                $thruName = $thruSegments[count($thruSegments)-1];
536
            }
537
            $first = $keyName;
538
            $last = $localName;
539
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
540
        }
541
    }
542
543
    /**
544
     * @param $rels
545
     * @param $hooks
546
     */
547
    private function getRelationshipsHasOne($rels, &$hooks)
548
    {
549
        foreach ($rels['HasOne'] as $property => $foo) {
550
            if ($foo instanceof MorphOne) {
551
                continue;
552
            }
553
            $isBelong = $foo instanceof BelongsTo;
554
            $mult = $isBelong ? '1' : '0..1';
555
            $targ = get_class($foo->getRelated());
556
557
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
558
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
559
560
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
561
            $keySegments = explode('.', $keyName);
562
            $keyName = $keySegments[count($keySegments)-1];
563
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
564
            $localSegments = explode('.', $localRaw);
565
            $localName = $localSegments[count($localSegments)-1];
566
            $first = $isBelong ? $localName : $keyName;
567
            $last = $isBelong ? $keyName : $localName;
568
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
569
        }
570
    }
571
572
    /**
573
     * @param $rels
574
     * @param $hooks
575
     */
576
    private function getRelationshipsKnownPolyMorph($rels, &$hooks)
577
    {
578
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
579
            $isMany = $foo instanceof MorphToMany;
580
            $targ = get_class($foo->getRelated());
581
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
582
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
583
584
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
585
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
586
587
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
588
            $keySegments = explode('.', $keyRaw);
589
            $keyName = $keySegments[count($keySegments)-1];
590
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
591
            $localSegments = explode('.', $localRaw);
592
            $localName = $localSegments[count($localSegments)-1];
593
            $first = $isMany ? $keyName : $localName;
594
            $last = $isMany ? $localName : $keyName;
595
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
596
        }
597
    }
598
599
    /**
600
     * @param $rels
601
     * @param $hooks
602
     */
603
    private function getRelationshipsUnknownPolyMorph($rels, &$hooks)
604
    {
605
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
606
            $isMany = $foo instanceof MorphToMany;
607
            $targ = get_class($foo->getRelated());
608
            $mult = $isMany ? '*' : '1';
609
610
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
611
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
612
613
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
614
            $keySegments = explode('.', $keyRaw);
615
            $keyName = $keySegments[count($keySegments)-1];
616
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
617
            $localSegments = explode('.', $localRaw);
618
            $localName = $localSegments[count($localSegments)-1];
619
620
            $first = $keyName;
621
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
622
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
623
        }
624
    }
625
626
    /**
627
     * SUpplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
628
     *
629
     * @return array
630
     */
631
    public function retrieveCasts()
632
    {
633
        if (method_exists($this, 'getCasts')) {
634
            return $this->getCasts();
0 ignored issues
show
Bug introduced by
It seems like getCasts() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
635
        }
636
        return $this->casts;
637
    }
638
639
    /**
640
     * Return list of relations to be eager-loaded by Laravel query provider.
641
     *
642
     * @return array
643
     */
644
    public function getEagerLoad()
645
    {
646
        assert(is_array($this->loadEagerRelations));
647
        return $this->loadEagerRelations;
648
    }
649
650
    /**
651
     * Set list of relations to be eager-loaded.
652
     *
653
     * @param array $relations
654
     */
655
    public function setEagerLoad(array $relations)
656
    {
657
        $check = array_map('strval', $relations);
658
        assert($relations == $check, 'All supplied relations must be resolvable to strings');
659
        $this->loadEagerRelations = $relations;
660
    }
661
662
    /*
663
     * Is this model the known side of at least one polymorphic relation?
664
     */
665
    public function isKnownPolymorphSide()
666
    {
667
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
668
        // you're turned around
669
        $rels = $this->getRelationshipsFromMethods();
670
        return !empty($rels['KnownPolyMorphSide']);
671
    }
672
673
    /*
674
     * Is this model on the unknown side of at least one polymorphic relation?
675
     */
676
    public function isUnknownPolymorphSide()
677
    {
678
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
679
        // you're turned around
680
        $rels = $this->getRelationshipsFromMethods();
681
        return !empty($rels['UnknownPolyMorphSide']);
682
    }
683
684
    /**
685
     * Extract entity gubbins detail for later downstream use.
686
     *
687
     * @return EntityGubbins
688
     */
689
    public function extractGubbins()
690
    {
691
        $multArray = [
692
            '*' => AssociationStubRelationType::MANY(),
693
            '1' => AssociationStubRelationType::ONE(),
694
            '0..1' => AssociationStubRelationType::NULL_ONE()
695
        ];
696
697
        $gubbins = new EntityGubbins();
698
        $gubbins->setName($this->getEndpointName());
699
        $gubbins->setClassName(get_class($this));
700
701
        $lowerNames = [];
702
703
        $fields = $this->metadata();
704
        $entityFields = [];
705
        foreach ($fields as $name => $field) {
706
            if (in_array(strtolower($name), $lowerNames)) {
707
                $msg = 'Property names must be unique, without regard to case';
708
                throw new \Exception($msg);
709
            }
710
            $lowerNames[] = strtolower($name);
711
            $nuField = new EntityField();
712
            $nuField->setName($name);
713
            $nuField->setIsNullable($field['nullable']);
714
            $nuField->setReadOnly(false);
715
            $nuField->setCreateOnly(false);
716
            $nuField->setDefaultValue($field['default']);
717
            $nuField->setIsKeyField($this->getKeyName() == $name);
718
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
719
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
720
            $entityFields[$name] = $nuField;
721
        }
722
        $isEmpty = (0 === count($entityFields));
723
        if (!($isEmpty && $this->isRunningInArtisan())) {
724
            $gubbins->setFields($entityFields);
725
        }
726
727
        $rawRels = $this->getRelationships();
728
        $stubs = [];
729
        foreach ($rawRels as $key => $rel) {
730
            foreach ($rel as $rawName => $deets) {
731
                foreach ($deets as $relName => $relGubbins) {
732
                    if (in_array(strtolower($relName), $lowerNames)) {
733
                        $msg = 'Property names must be unique, without regard to case';
734
                        throw new \Exception($msg);
735
                    }
736
                    $lowerNames[] = strtolower($relName);
737
                    $gubbinsType = $relGubbins['type'];
738
                    $property = $relGubbins['property'];
739
                    $isPoly = isset($gubbinsType);
740
                    $targType = 'known' != $gubbinsType ? $rawName : null;
741
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
742
                    $stub->setBaseType(get_class($this));
743
                    $stub->setRelationName($property);
744
                    $stub->setKeyField($relGubbins['local']);
745
                    $stub->setForeignField($targType ? $key : null);
746
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
747
                    $stub->setTargType($targType);
748
                    if (null !== $relGubbins['through']) {
749
                        $stub->setThroughField($relGubbins['through']);
750
                    }
751
                    assert($stub->isOk());
752
                    $stubs[$property] = $stub;
753
                }
754
            }
755
        }
756
        $gubbins->setStubs($stubs);
757
758
        return $gubbins;
759
    }
760
761
    public function synthLiteralPK()
762
    {
763
        if (!$this->isKnownPolymorphSide()) {
764
            return;
765
        }
766
        $fieldName = LaravelReadQuery::PK;
767
        $this->$fieldName = $this->getKey();
768
    }
769
770
    public function isRunningInArtisan()
771
    {
772
        return App::runningInConsole() && !App::runningUnitTests();
773
    }
774
775
    protected function getTableColumns()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
776
    {
777
        if (0 === count(self::$tableColumns)) {
778
            $table = $this->getTable();
779
            $connect = $this->getConnection();
780
            $builder = $connect->getSchemaBuilder();
781
            $columns = $builder->getColumnListing($table);
782
783
            self::$tableColumns = $columns;
784
        }
785
        return self::$tableColumns;
786
    }
787
788
    protected function getTableDoctrineColumns()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
789
    {
790
        if (0 === count(self::$tableColumnsDoctrine)) {
791
            $table = $this->getTable();
792
            $connect = $this->getConnection();
793
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
794
795
            self::$tableColumnsDoctrine = $columns;
796
        }
797
        return self::$tableColumnsDoctrine;
798
    }
799
800
    public function reset()
801
    {
802
        self::$tableData = [];
803
        self::$tableColumnsDoctrine = [];
804
        self::$tableColumns = [];
805
    }
806
}
807