Passed
Pull Request — master (#154)
by Alex
07:05
created

MetadataTrait::addRelationsHook()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 0
cp 0
rs 8.9713
c 0
b 0
f 0
cc 3
eloc 11
nc 4
nop 9
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
use Mockery\Mock;
22
23
trait MetadataTrait
24
{
25
    protected static $relationHooks = [];
26
    protected static $relationCategories = [];
27
    protected static $methodPrimary = [];
28 3
    protected static $methodAlternate = [];
29
    protected $loadEagerRelations = [];
30 3
    protected static $tableColumns = [];
31
    protected static $tableColumnsDoctrine = [];
32
    protected static $tableData = [];
33 2
    protected static $dontCastTypes = ['object', 'array', 'collection', 'int'];
34 2
35
    /*
36 2
     * Retrieve and assemble this model's metadata for OData packaging
37
     */
38 2
    public function metadata()
39 1
    {
40
        assert($this instanceof Model, get_class($this));
41
42 1
        // Break these out separately to enable separate reuse
43 1
        $connect = $this->getConnection();
44 1
        $builder = $connect->getSchemaBuilder();
45
46 1
        $table = $this->getTable();
47
48 1
        if (!$builder->hasTable($table)) {
49 1
            return [];
50 1
        }
51
        if (0 !== count(self::$tableData)) {
52 1
            return self::$tableData;
53 1
        }
54 1
55
        $columns = $this->getTableColumns();
56 1
        $mask = $this->metadataMask();
57
        $columns = array_intersect($columns, $mask);
58 1
59 1
        $tableData = [];
60 1
61 1
        $rawFoo = $this->getTableDoctrineColumns();
62 1
        $foo = [];
63 1
        $getters = $this->collectGetters();
64 1
        $getters = array_intersect($getters, $mask);
65
        $casts = $this->retrieveCasts();
66 1
67
        foreach ($rawFoo as $key => $val) {
68
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
69
            $key = trim($key, '`');
70
            $foo[$key] = $val;
71
        }
72
73 4
        foreach ($columns as $column) {
74
            // Doctrine schema manager returns columns with lowercased names
75 4
            $rawColumn = $foo[strtolower($column)];
76
            $nullable = !($rawColumn->getNotNull());
77 4
            $fillable = in_array($column, $this->getFillable());
78 4
            $rawType = $rawColumn->getType();
79 4
            $type = $rawType->getName();
80 2
            $default = $this->$column;
81 4
            $tableData[$column] = ['type' => $type,
82 1
                'nullable' => $nullable,
83 1
                'fillable' => $fillable,
84
                'default' => $default
85 4
            ];
86
        }
87
88
        foreach ($getters as $get) {
89
            if (isset($tableData[$get])) {
90
                continue;
91 5
            }
92
            $default = $this->$get;
93 5
            $tableData[$get] = ['type' => 'text', 'nullable' => true, 'fillable' => false, 'default' => $default];
94 5
        }
95 1
96
        // now, after everything's gathered up, apply Eloquent model's $cast array
97
        foreach ($casts as $key => $type) {
98 4
            $type = strtolower($type);
99
            if (array_key_exists($key, $tableData) && !in_array($type, self::$dontCastTypes)) {
100 4
                $tableData[$key]['type'] = $type;
101
            }
102 4
        }
103 4
104 4
        self::$tableData = $tableData;
105 4
        return $tableData;
106 4
    }
107 4
108
    /*
109 4
     * Return the set of fields that are permitted to be in metadata
110 1
     * - following same visible-trumps-hidden guideline as Laravel
111 1
     */
112 1
    public function metadataMask()
113 1
    {
114
        $attribs = array_keys($this->getAllAttributes());
115 4
116 4
        $visible = $this->getVisible();
117
        $hidden = $this->getHidden();
118 4
        if (0 < count($visible)) {
119
            $attribs = array_intersect($visible, $attribs);
120
        } elseif (0 < count($hidden)) {
121
            $attribs = array_diff($attribs, $hidden);
122
        }
123
124
        return $attribs;
125
    }
126
127
    /*
128
     * Get the endpoint name being exposed
129
     *
130
     * @return null|string;
131
     */
132
    public function getEndpointName()
133
    {
134
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
135
136
        if (!isset($endpoint)) {
137
            $bitter = get_class($this);
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, /** @scrutinizer ignore-type */$begin, strrpos($code, '}')-$begin+1);
238 3
                        $lastCode = $code[strlen(/** @scrutinizer ignore-type */$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 (stripos(/** @scrutinizer ignore-type */$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(/** @scrutinizer ignore-type */$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
        $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(
486
        &$hooks,
487
        $first,
488
        $property,
489
        $last,
490
        $mult,
491
        $targ,
492
        $type = null,
493
        $through = null,
494
        array $pivot = null
495
    ) {
496
        if (!isset($hooks[$first])) {
497
            $hooks[$first] = [];
498
        }
499
        if (!isset($hooks[$first][$targ])) {
500
            $hooks[$first][$targ] = [];
501
        }
502
        $hooks[$first][$targ][$property] = [
503
            'property' => $property,
504
            'local' => $last,
505
            'through' => $through,
506
            'multiplicity' => $mult,
507
            'type' => $type,
508
            'pivot' => $pivot
509
        ];
510
    }
511
512
    /**
513
     * @param $rels
514
     * @param $hooks
515
     */
516
    private function getRelationshipsHasMany($rels, &$hooks)
517
    {
518
        foreach ($rels['HasMany'] as $property => $foo) {
519
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
520
                continue;
521
            }
522
            $pivotColumns = $this->getPivotColumns($foo);
523
            $mult = '*';
524
            $targ = get_class($foo->getRelated());
525
            list($thruName, $fkMethodName, $rkMethodName) = $this->getRelationsHasManyKeyNames($foo);
526
527
            $keyRaw = $foo->$fkMethodName();
528
            $keySegments = explode('.', $keyRaw);
529
            $keyName = $keySegments[count($keySegments)-1];
530
            $localRaw = $foo->$rkMethodName();
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, $pivotColumns);
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;
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), 'LoadEagerRelations not an array');
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());
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(), 'Generated stub not consistent');
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
        /* As MetadataTrait is assumed to be deployed on top of an Eloquent model, which has getKey(), this is a
769
           false positive */
770
        /** @scrutinizer ignore-call */
771
        $this->$fieldName = $this->getKey();
772
    }
773
774
    public function isRunningInArtisan()
775
    {
776
        return App::runningInConsole() && !App::runningUnitTests();
777
    }
778
779
    protected function getTableColumns()
780
    {
781
        if (0 === count(self::$tableColumns)) {
782
            $table = $this->getTable();
783
            $connect = $this->getConnection();
784
            $builder = $connect->getSchemaBuilder();
785
            $columns = $builder->getColumnListing($table);
786
787
            self::$tableColumns = $columns;
788
        }
789
        return self::$tableColumns;
790
    }
791
792
    protected function getTableDoctrineColumns()
793
    {
794
        if (0 === count(self::$tableColumnsDoctrine)) {
795
            $table = $this->getTable();
796
            $connect = $this->getConnection();
797
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
798
799
            self::$tableColumnsDoctrine = $columns;
800
        }
801
        return self::$tableColumnsDoctrine;
802
    }
803
804
    public function reset()
805
    {
806
        self::$tableData = [];
807
        self::$tableColumnsDoctrine = [];
808
        self::$tableColumns = [];
809
    }
810
811
    private function getRelationsHasManyKeyNames($foo)
812
    {
813
        $thruName = null;
814
        if ($foo instanceof HasManyThrough) {
815
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
816
            $thruName = $this->polyglotThroughKeyMethodNames($foo);
817
            return array($thruName, $fkMethodName, $rkMethodName);
818
        } elseif ($foo instanceof BelongsToMany) {
819
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, true);
820
            return array($thruName, $fkMethodName, $rkMethodName);
821
        } else {
822
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
823
            return array($thruName, $fkMethodName, $rkMethodName);
824
        }
825
    }
826
827
    private function getPivotColumns(Relation $foo)
828
    {
829
        $pivotColumns = null;
830
        if ($foo instanceof BelongsToMany) {
831
            $arr = ((array)$foo);
832
            $prefix = chr(0) . '*' . chr(0);
833
            $pivotColumns = $arr[$prefix . 'pivotColumns'];
834
        }
835
        return 0 == count($pivotColumns) ? null : $pivotColumns;
836
    }
837
}
838