Passed
Pull Request — master (#171)
by Alex
04: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
eloc 10
dl 0
loc 14
ccs 0
cts 0
cp 0
rs 9.9332
c 0
b 0
f 0
cc 3
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
use Mockery\Mock;
22
use POData\Common\InvalidOperationException;
23
24
trait MetadataTrait
25
{
26
    protected static $relationHooks = [];
27
    protected static $relationCategories = [];
28 3
    protected static $methodPrimary = [];
29
    protected static $methodAlternate = [];
30 3
    protected $loadEagerRelations = [];
31
    protected static $tableColumns = [];
32
    protected static $tableColumnsDoctrine = [];
33 2
    protected static $tableData = [];
34 2
    protected static $dontCastTypes = ['object', 'array', 'collection', 'int'];
35
36 2
    /*
37
     * Retrieve and assemble this model's metadata for OData packaging
38 2
     */
39 1
    public function metadata()
40
    {
41
        if (!$this instanceof Model) {
42 1
            throw new InvalidOperationException(get_class($this));
43 1
        }
44 1
45
        if (0 !== count(self::$tableData)) {
46 1
            return self::$tableData;
47
        } elseif (isset($this->odata)) {
48 1
            return self::$tableData = $this->odata;
49 1
        }
50 1
51
        // Break these out separately to enable separate reuse
52 1
        $connect = $this->getConnection();
53 1
        $builder = $connect->getSchemaBuilder();
54 1
55
        $table = $this->getTable();
56 1
57
        if (!$builder->hasTable($table)) {
58 1
            return self::$tableData = [];
59 1
        }
60 1
61 1
        $columns = $this->getTableColumns();
62 1
        $mask = $this->metadataMask();
63 1
        $columns = array_intersect($columns, $mask);
0 ignored issues
show
Bug introduced by
It seems like $mask can also be of type mixed and Illuminate\Database\Eloquent\Builder; however, parameter $array2 of array_intersect() does only seem to accept array, 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

63
        $columns = array_intersect($columns, /** @scrutinizer ignore-type */ $mask);
Loading history...
Bug introduced by
It seems like $columns can also be of type mixed and Illuminate\Database\Eloquent\Builder; however, parameter $array1 of array_intersect() does only seem to accept array, 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

63
        $columns = array_intersect(/** @scrutinizer ignore-type */ $columns, $mask);
Loading history...
64 1
65
        $tableData = [];
66 1
67
        $rawFoo = $this->getTableDoctrineColumns();
68
        $foo = [];
69
        $getters = $this->collectGetters();
70
        $getters = array_intersect($getters, $mask);
71
        $casts = $this->retrieveCasts();
72
73 4
        foreach ($rawFoo as $key => $val) {
74
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
75 4
            $key = trim($key, '`');
76
            $foo[$key] = $val;
77 4
        }
78 4
79 4
        foreach ($columns as $column) {
80 2
            // Doctrine schema manager returns columns with lowercased names
81 4
            $rawColumn = $foo[strtolower($column)];
82 1
            $nullable = !($rawColumn->getNotNull());
83 1
            $fillable = in_array($column, $this->getFillable());
84
            $rawType = $rawColumn->getType();
85 4
            $type = $rawType->getName();
86
            $default = $this->$column;
87
            $tableData[$column] = ['type' => $type,
88
                'nullable' => $nullable,
89
                'fillable' => $fillable,
90
                'default' => $default
91 5
            ];
92
        }
93 5
94 5
        foreach ($getters as $get) {
95 1
            if (isset($tableData[$get])) {
96
                continue;
97
            }
98 4
            $default = $this->$get;
99
            $tableData[$get] = ['type' => 'text', 'nullable' => true, 'fillable' => false, 'default' => $default];
100 4
        }
101
102 4
        // now, after everything's gathered up, apply Eloquent model's $cast array
103 4
        foreach ($casts as $key => $type) {
104 4
            $type = strtolower($type);
105 4
            if (array_key_exists($key, $tableData) && !in_array($type, self::$dontCastTypes)) {
106 4
                $tableData[$key]['type'] = $type;
107 4
            }
108
        }
109 4
110 1
        self::$tableData = $tableData;
111 1
        return $tableData;
112 1
    }
113 1
114
    /**
115 4
     * Return the set of fields that are permitted to be in metadata
116 4
     * - following same visible-trumps-hidden guideline as Laravel
117
     *
118 4
     * @return array
119
     */
120
    public function metadataMask()
121
    {
122
        $attribs = array_keys($this->getAllAttributes());
123
124
        $visible = $this->getVisible();
125
        $hidden = $this->getHidden();
126
        if (0 < count($visible)) {
127
            $attribs = array_intersect($visible, $attribs);
128
        } elseif (0 < count($hidden)) {
129
            $attribs = array_diff($attribs, $hidden);
130
        }
131
132
        return $attribs;
133
    }
134
135
    /*
136
     * Get the endpoint name being exposed
137
     *
138
     * @return null|string;
139
     */
140
    public function getEndpointName()
141
    {
142
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
143
144
        if (!isset($endpoint)) {
145
            $bitter = get_class($this);
146
            $name = substr($bitter, strrpos($bitter, '\\')+1);
147
            return ($name);
148
        }
149
        return ($endpoint);
150
    }
151
152
    /**
153
     * Get model's relationships.
154
     *
155
     * @return array
156
     */
157
    public function getRelationships()
158
    {
159
        if (empty(static::$relationHooks)) {
160
            $hooks = [];
161
162
            $rels = $this->getRelationshipsFromMethods(true);
163
164
            $this->getRelationshipsUnknownPolyMorph($rels, $hooks);
165
166
            $this->getRelationshipsKnownPolyMorph($rels, $hooks);
167
168
            $this->getRelationshipsHasOne($rels, $hooks);
169
170
            $this->getRelationshipsHasMany($rels, $hooks);
171
172
            static::$relationHooks = $hooks;
173
        }
174
175 3
        return static::$relationHooks;
176
    }
177 3
178
    protected function getAllAttributes()
179 3
    {
180 3
        // Adapted from http://stackoverflow.com/a/33514981
181 3
        // $columns = $this->getFillable();
182 3
        // Another option is to get all columns for the table like so:
183 3
        $columns = $this->getTableColumns();
184 3
        // but it's safer to just get the fillable fields
185 3
186 3
        $attributes = $this->getAttributes();
187 3
188 3
        foreach ($columns as $column) {
189
            if (!array_key_exists($column, $attributes)) {
190 3
                $attributes[$column] = null;
191
            }
192 3
        }
193 3
194 3
        $methods = $this->collectGetters();
195 3
196 3
        foreach ($methods as $method) {
197 3
            $attributes[$method] = null;
198 3
        }
199 3
200 3
        return $attributes;
201 3
    }
202
203 3
    /**
204 3
     * @param bool $biDir
205 3
     *
206 3
     * @return array
207 3
     */
208 3
    protected function getRelationshipsFromMethods($biDir = false)
209 3
    {
210 3
        $biDirVal = intval($biDir);
211
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
212 3
        if (!$isCached) {
213 3
            $model = $this;
214 3
            $relationships = [
215
                'HasOne' => [],
216 3
                'UnknownPolyMorphSide' => [],
217 3
                'HasMany' => [],
218 3
                'KnownPolyMorphSide' => []
219 3
            ];
220 3
            $methods = get_class_methods($model);
221
            if (!empty($methods)) {
222 1
                foreach ($methods as $method) {
223 3
                    if (!method_exists('Illuminate\Database\Eloquent\Model', $method)
224
                        && !method_exists(Mock::class, $method)
225 1
                        && !method_exists(MetadataTrait::class, $method)
226
                    ) {
227 1
                        //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
228
                        $reflection = new \ReflectionMethod($model, $method);
229 1
                        $fileName = $reflection->getFileName();
230
231 3
                        $file = new \SplFileObject($fileName);
232 2
                        $file->seek($reflection->getStartLine()-1);
233 2
                        $code = '';
234 3
                        while ($file->key() < $reflection->getEndLine()) {
235 3
                            $code .= $file->current();
236 3
                            $file->next();
237 3
                        }
238 3
239 3
                        $code = trim(preg_replace('/\s\s+/', '', $code));
240 3
                        if (false === stripos($code, 'function')) {
241
                            $msg = 'Function definition must have keyword \'function\'';
242
                            throw new InvalidOperationException($msg);
243
                        }
244
                        $begin = strpos($code, 'function(');
245
                        $code = substr($code, /** @scrutinizer ignore-type */$begin, strrpos($code, '}')-$begin+1);
246
                        $lastCode = $code[strlen(/** @scrutinizer ignore-type */$code)-1];
247
                        if ('}' != $lastCode) {
248
                            $msg = 'Final character of function definition must be closing brace';
249
                            throw new InvalidOperationException($msg);
250
                        }
251
                        foreach ([
252
                                     'hasMany',
253
                                     'hasManyThrough',
254
                                     'belongsToMany',
255
                                     'hasOne',
256
                                     'belongsTo',
257
                                     'morphOne',
258
                                     'morphTo',
259
                                     'morphMany',
260
                                     'morphToMany',
261
                                     'morphedByMany'
262
                                 ] as $relation) {
263
                            $search = '$this->' . $relation . '(';
264
                            if (stripos(/** @scrutinizer ignore-type */$code, $search)) {
265
                                //Resolve the relation's model to a Relation object.
266
                                $relationObj = $model->$method();
267
                                if ($relationObj instanceof Relation) {
268
                                    $relObject = $relationObj->getRelated();
269
                                    $relatedModel = '\\' . get_class($relObject);
270
                                    if (in_array(MetadataTrait::class, class_uses($relatedModel))) {
271
                                        $relations = [
272
                                            'hasManyThrough',
273
                                            'belongsToMany',
274
                                            'hasMany',
275
                                            'morphMany',
276
                                            'morphToMany',
277
                                            'morphedByMany'
278
                                        ];
279
                                        if (in_array($relation, $relations)) {
280
                                            //Collection or array of models (because Collection is Arrayable)
281
                                            $relationships['HasMany'][$method] = $biDir ? $relationObj : $relatedModel;
282
                                        } elseif ('morphTo' === $relation) {
283
                                            // Model isn't specified because relation is polymorphic
284
                                            $relationships['UnknownPolyMorphSide'][$method] =
285
                                                $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
286
                                        } else {
287
                                            //Single model is returned
288
                                            $relationships['HasOne'][$method] = $biDir ? $relationObj : $relatedModel;
289
                                        }
290
                                        if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
291
                                            $relationships['KnownPolyMorphSide'][$method] =
292
                                                $biDir ? $relationObj : $relatedModel;
293
                                        }
294
                                        if (in_array($relation, ['morphedByMany'])) {
295
                                            $relationships['UnknownPolyMorphSide'][$method] =
296
                                                $biDir ? $relationObj : $relatedModel;
297
                                        }
298
                                    }
299
                                }
300
                            }
301
                        }
302
                    }
303
                }
304
            }
305
            static::$relationCategories[$biDirVal] = $relationships;
306
        }
307
        return static::$relationCategories[$biDirVal];
308
    }
309
310
    /**
311
     * Get the visible attributes for the model.
312
     *
313
     * @return array
314
     */
315
    abstract public function getVisible();
316
317
    /**
318
     * Get the hidden attributes for the model.
319
     *
320
     * @return array
321
     */
322
    abstract public function getHidden();
323
324
    /**
325
     * Get the primary key for the model.
326
     *
327
     * @return string
328
     */
329
    abstract public function getKeyName();
330
331
    /**
332
     * Get the current connection name for the model.
333
     *
334
     * @return string
335
     */
336
    abstract public function getConnectionName();
337
338
    /**
339
     * Get the database connection for the model.
340
     *
341
     * @return \Illuminate\Database\Connection
342
     */
343
    abstract public function getConnection();
344
345
    /**
346
     * Get all of the current attributes on the model.
347
     *
348
     * @return array
349
     */
350
    abstract public function getAttributes();
351
352
    /**
353
     * Get the table associated with the model.
354
     *
355
     * @return string
356
     */
357
    abstract public function getTable();
358
359
    /**
360
     * Get the fillable attributes for the model.
361
     *
362
     * @return array
363
     */
364
    abstract public function getFillable();
365
366
    /**
367
     * Dig up all defined getters on the model.
368
     *
369
     * @return array
370
     */
371
    protected function collectGetters()
372
    {
373
        $getterz = [];
374
        $methods = get_class_methods($this);
375
        foreach ($methods as $method) {
376
            if (12 < strlen($method) && 'get' == substr($method, 0, 3)) {
377
                if ('Attribute' == substr($method, -9)) {
378
                    $getterz[] = $method;
379
                }
380
            }
381
        }
382
        $methods = [];
383
384
        foreach ($getterz as $getter) {
385
            $residual = substr($getter, 3);
386
            $residual = substr(/** @scrutinizer ignore-type */$residual, 0, -9);
387
            $methods[] = $residual;
388
        }
389
        return $methods;
390
    }
391
392
    /**
393
     * @param       $foo
394
     * @param mixed $condition
395
     *
396
     * @return array
397
     */
398
    private function polyglotKeyMethodNames($foo, $condition = false)
399
    {
400
        $fkList = ['getQualifiedForeignKeyName', 'getForeignKey'];
401
        $rkList = ['getQualifiedRelatedKeyName', 'getOtherKey', 'getOwnerKey'];
402
403
        $fkMethodName = null;
404
        $rkMethodName = null;
405
        if ($condition) {
406
            if (array_key_exists(get_class($foo), static::$methodPrimary)) {
407
                $line = static::$methodPrimary[get_class($foo)];
408
                $fkMethodName = $line['fk'];
409
                $rkMethodName = $line['rk'];
410
            } else {
411
                $methodList = get_class_methods(get_class($foo));
412
                $fkMethodName = 'getQualifiedForeignPivotKeyName';
413
                foreach ($fkList as $option) {
414
                    if (in_array($option, $methodList)) {
415
                        $fkMethodName = $option;
416
                        break;
417
                    }
418
                }
419
                if (!(in_array($fkMethodName, $methodList))) {
420
                    $msg = 'Selected method, ' . $fkMethodName . ', not in method list';
421
                    throw new InvalidOperationException($msg);
422
                }
423
                $rkMethodName = 'getQualifiedRelatedPivotKeyName';
424
                foreach ($rkList as $option) {
425
                    if (in_array($option, $methodList)) {
426
                        $rkMethodName = $option;
427
                        break;
428
                    }
429
                }
430
                if (!(in_array($rkMethodName, $methodList))) {
431
                    $msg = 'Selected method, ' . $rkMethodName . ', not in method list';
432
                    throw new InvalidOperationException($msg);
433
                }
434
                $line = ['fk' => $fkMethodName, 'rk' => $rkMethodName];
435
                static::$methodPrimary[get_class($foo)] = $line;
436
            }
437
        }
438
        return [$fkMethodName, $rkMethodName];
439
    }
440
441
    private function polyglotKeyMethodBackupNames($foo, $condition = false)
442
    {
443
        $fkList = ['getForeignKey', 'getForeignKeyName', 'getQualifiedFarKeyName'];
444
        $rkList = ['getOtherKey', 'getQualifiedParentKeyName'];
445
446
        $fkMethodName = null;
447
        $rkMethodName = null;
448
        if ($condition) {
449
            if (array_key_exists(get_class($foo), static::$methodAlternate)) {
450
                $line = static::$methodAlternate[get_class($foo)];
451
                $fkMethodName = $line['fk'];
452
                $rkMethodName = $line['rk'];
453
            } else {
454
                $methodList = get_class_methods(get_class($foo));
455
                $fkCombo = array_values(array_intersect($fkList, $methodList));
456
                if (!(1 <= count($fkCombo))) {
457
                    $msg = 'Expected at least 1 element in foreign-key list, got ' . count($fkCombo);
458
                    throw new InvalidOperationException($msg);
459
                }
460
                $fkMethodName = $fkCombo[0];
461
                if (!(in_array($fkMethodName, $methodList))) {
462
                    $msg = 'Selected method, ' . $fkMethodName . ', not in method list';
463
                    throw new InvalidOperationException($msg);
464
                }
465
                $rkCombo = array_values(array_intersect($rkList, $methodList));
466
                if (!(1 <= count($rkCombo))) {
467
                    $msg = 'Expected at least 1 element in related-key list, got ' . count($rkCombo);
468
                    throw new InvalidOperationException($msg);
469
                }
470
                $rkMethodName = $rkCombo[0];
471
                if (!(in_array($rkMethodName, $methodList))) {
472
                    $msg = 'Selected method, ' . $rkMethodName . ', not in method list';
473
                    throw new InvalidOperationException($msg);
474
                }
475
                $line = ['fk' => $fkMethodName, 'rk' => $rkMethodName];
476
                static::$methodAlternate[get_class($foo)] = $line;
477
            }
478
        }
479
        return [$fkMethodName, $rkMethodName];
480
    }
481
482
    private function polyglotThroughKeyMethodNames(HasManyThrough $foo)
483
    {
484
        $thruList = ['getThroughKey', 'getQualifiedFirstKeyName'];
485
486
        $methodList = get_class_methods(get_class($foo));
487
        $thruCombo = array_values(array_intersect($thruList, $methodList));
488
        return $thruCombo[0];
489
    }
490
491
492
    /**
493
     * @param             $hooks
494
     * @param             $first
495
     * @param             $property
496
     * @param             $last
497
     * @param             $mult
498
     * @param             $targ
499
     * @param string|null $targ
500
     * @param null|mixed  $type
501
     */
502
    private function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ, $type = null, $through = null)
503
    {
504
        if (!isset($hooks[$first])) {
505
            $hooks[$first] = [];
506
        }
507
        if (!isset($hooks[$first][$targ])) {
508
            $hooks[$first][$targ] = [];
509
        }
510
        $hooks[$first][$targ][$property] = [
511
            'property' => $property,
512
            'local' => $last,
513
            'through' => $through,
514
            'multiplicity' => $mult,
515
            'type' => $type
516
        ];
517
    }
518
519
    /**
520
     * @param $rels
521
     * @param $hooks
522
     */
523
    private function getRelationshipsHasMany($rels, &$hooks)
524
    {
525
        foreach ($rels['HasMany'] as $property => $foo) {
526
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
527
                continue;
528
            }
529
            $mult = '*';
530
            $targ = get_class($foo->getRelated());
531
            list($thruName, $fkMethodName, $rkMethodName) = $this->getRelationsHasManyKeyNames($foo);
532
533
            $keyRaw = $foo->$fkMethodName();
534
            $keySegments = explode('.', $keyRaw);
535
            $keyName = $keySegments[count($keySegments)-1];
536
            $localRaw = $foo->$rkMethodName();
537
            $localSegments = explode('.', $localRaw);
538
            $localName = $localSegments[count($localSegments)-1];
539
            if (null !== $thruName) {
540
                $thruRaw = $foo->$thruName();
541
                $thruSegments = explode('.', $thruRaw);
542
                $thruName = $thruSegments[count($thruSegments)-1];
543
            }
544
            $first = $keyName;
545
            $last = $localName;
546
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
547
        }
548
    }
549
550
    /**
551
     * @param $rels
552
     * @param $hooks
553
     */
554
    private function getRelationshipsHasOne($rels, &$hooks)
555
    {
556
        foreach ($rels['HasOne'] as $property => $foo) {
557
            if ($foo instanceof MorphOne) {
558
                continue;
559
            }
560
            $isBelong = $foo instanceof BelongsTo;
561
            $mult = $isBelong ? '1' : '0..1';
562
            $targ = get_class($foo->getRelated());
563
564
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
565
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
566
567
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
568
            $keySegments = explode('.', $keyName);
569
            $keyName = $keySegments[count($keySegments)-1];
570
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
571
            $localSegments = explode('.', $localRaw);
572
            $localName = $localSegments[count($localSegments)-1];
573
            $first = $isBelong ? $localName : $keyName;
574
            $last = $isBelong ? $keyName : $localName;
575
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
576
        }
577
    }
578
579
    /**
580
     * @param $rels
581
     * @param $hooks
582
     */
583
    private function getRelationshipsKnownPolyMorph($rels, &$hooks)
584
    {
585
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
586
            $isMany = $foo instanceof MorphToMany;
587
            $targ = get_class($foo->getRelated());
588
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
589
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
590
591
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
592
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
593
594
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
595
            $keySegments = explode('.', $keyRaw);
596
            $keyName = $keySegments[count($keySegments)-1];
597
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
598
            $localSegments = explode('.', $localRaw);
599
            $localName = $localSegments[count($localSegments)-1];
600
            $first = $isMany ? $keyName : $localName;
601
            $last = $isMany ? $localName : $keyName;
602
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
603
        }
604
    }
605
606
    /**
607
     * @param $rels
608
     * @param $hooks
609
     */
610
    private function getRelationshipsUnknownPolyMorph($rels, &$hooks)
611
    {
612
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
613
            $isMany = $foo instanceof MorphToMany;
614
            $targ = get_class($foo->getRelated());
615
            $mult = $isMany ? '*' : '1';
616
617
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
618
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
619
620
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
621
            $keySegments = explode('.', $keyRaw);
622
            $keyName = $keySegments[count($keySegments)-1];
623
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
624
            $localSegments = explode('.', $localRaw);
625
            $localName = $localSegments[count($localSegments)-1];
626
627
            $first = $keyName;
628
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
629
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
630
        }
631
    }
632
633
    /**
634
     * Supplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
635
     *
636
     * @return array
637
     */
638
    public function retrieveCasts()
639
    {
640
        $exists = method_exists($this, 'getCasts');
641
        return $exists ? (array)$this->getCasts() : (array)$this->casts;
642
    }
643
644
    /**
645
     * Return list of relations to be eager-loaded by Laravel query provider.
646
     *
647
     * @return array
648
     */
649
    public function getEagerLoad()
650
    {
651
        return $this->loadEagerRelations;
652
    }
653
654
    /**
655
     * Set list of relations to be eager-loaded.
656
     *
657
     * @param array $relations
658
     */
659
    public function setEagerLoad(array $relations)
660
    {
661
        $check = array_map('strval', $relations);
662
        if ($relations != $check) {
663
            throw new InvalidOperationException('All supplied relations must be resolvable to strings');
664
        }
665
666
        $this->loadEagerRelations = $relations;
667
    }
668
669
    /*
670
     * Is this model the known side of at least one polymorphic relation?
671
     */
672
    public function isKnownPolymorphSide()
673
    {
674
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
675
        // you're turned around
676
        $rels = $this->getRelationshipsFromMethods();
677
        return !empty($rels['KnownPolyMorphSide']);
678
    }
679
680
    /*
681
     * Is this model on the unknown side of at least one polymorphic relation?
682
     */
683
    public function isUnknownPolymorphSide()
684
    {
685
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
686
        // you're turned around
687
        $rels = $this->getRelationshipsFromMethods();
688
        return !empty($rels['UnknownPolyMorphSide']);
689
    }
690
691
    /**
692
     * Extract entity gubbins detail for later downstream use.
693
     *
694
     * @return EntityGubbins
695
     */
696
    public function extractGubbins()
697
    {
698
        $multArray = [
699
            '*' => AssociationStubRelationType::MANY(),
700
            '1' => AssociationStubRelationType::ONE(),
701
            '0..1' => AssociationStubRelationType::NULL_ONE()
702
        ];
703
704
        $gubbins = new EntityGubbins();
705
        $gubbins->setName($this->getEndpointName());
706
        $gubbins->setClassName(get_class($this));
707
708
        $lowerNames = [];
709
710
        $fields = $this->metadata();
711
        $entityFields = [];
712
        foreach ($fields as $name => $field) {
713
            if (in_array(strtolower($name), $lowerNames)) {
714
                $msg = 'Property names must be unique, without regard to case';
715
                throw new \Exception($msg);
716
            }
717
            $lowerNames[] = strtolower($name);
718
            $nuField = new EntityField();
719
            $nuField->setName($name);
720
            $nuField->setIsNullable($field['nullable']);
721
            $nuField->setReadOnly(false);
722
            $nuField->setCreateOnly(false);
723
            $nuField->setDefaultValue($field['default']);
724
            $nuField->setIsKeyField($this->getKeyName() == $name);
725
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
726
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
727
            $entityFields[$name] = $nuField;
728
        }
729
        $isEmpty = (0 === count($entityFields));
730
        if (!($isEmpty && $this->isRunningInArtisan())) {
731
            $gubbins->setFields($entityFields);
732
        }
733
734
        $rawRels = $this->getRelationships();
735
        $stubs = [];
736
        foreach ($rawRels as $key => $rel) {
737
            foreach ($rel as $rawName => $deets) {
738
                foreach ($deets as $relName => $relGubbins) {
739
                    if (in_array(strtolower($relName), $lowerNames)) {
740
                        $msg = 'Property names must be unique, without regard to case';
741
                        throw new \Exception($msg);
742
                    }
743
                    $lowerNames[] = strtolower($relName);
744
                    $gubbinsType = $relGubbins['type'];
745
                    $property = $relGubbins['property'];
746
                    $isPoly = isset($gubbinsType);
747
                    $targType = 'known' != $gubbinsType ? $rawName : null;
748
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
749
                    $stub->setBaseType(get_class($this));
750
                    $stub->setRelationName($property);
751
                    $stub->setKeyField($relGubbins['local']);
752
                    $stub->setForeignField($targType ? $key : null);
753
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
754
                    $stub->setTargType($targType);
755
                    if (null !== $relGubbins['through']) {
756
                        $stub->setThroughField($relGubbins['through']);
757
                    }
758
                    if (!$stub->isOk()) {
759
                        throw new InvalidOperationException('Generated stub not consistent');
760
                    }
761
                    $stubs[$property] = $stub;
762
                }
763
            }
764
        }
765
        $gubbins->setStubs($stubs);
766
767
        return $gubbins;
768
    }
769
770
    public function isRunningInArtisan()
771
    {
772
        return App::runningInConsole() && !App::runningUnitTests();
773
    }
774
775
    /**
776
     * Get columns for selected table
777
     *
778
     * @return array
779
     */
780
    protected function getTableColumns()
781
    {
782
        if (0 === count(self::$tableColumns)) {
783
            $table = $this->getTable();
784
            $connect = $this->getConnection();
785
            $builder = $connect->getSchemaBuilder();
786
            $columns = $builder->getColumnListing($table);
787
788
            self::$tableColumns = (array)$columns;
789
        }
790
        return self::$tableColumns;
791
    }
792
793
    /**
794
     * Get Doctrine columns for selected table
795
     *
796
     * @return array
797
     */
798
    protected function getTableDoctrineColumns()
799
    {
800
        if (0 === count(self::$tableColumnsDoctrine)) {
801
            $table = $this->getTable();
802
            $connect = $this->getConnection();
803
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
804
805
            self::$tableColumnsDoctrine = $columns;
806
        }
807
        return self::$tableColumnsDoctrine;
808
    }
809
810
    public function reset()
811
    {
812
        self::$tableData = [];
813
        self::$tableColumnsDoctrine = [];
814
        self::$tableColumns = [];
815
    }
816
817
    /**
818
     * @return array|null
819
     */
820
    private function getRelationsHasManyKeyNames($foo)
821
    {
822
        $thruName = null;
823
        if ($foo instanceof HasManyThrough) {
824
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
825
            $thruName = $this->polyglotThroughKeyMethodNames($foo);
826
            return array($thruName, $fkMethodName, $rkMethodName);
827
        } elseif ($foo instanceof BelongsToMany) {
828
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, true);
829
            return array($thruName, $fkMethodName, $rkMethodName);
830
        } else {
831
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
832
            return array($thruName, $fkMethodName, $rkMethodName);
833
        }
834
    }
835
}
836