Completed
Push — master ( 83f777...faddc6 )
by Alex
14s
created

MetadataTrait::getRelationsHasManyKeyNames()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 3
nop 1
crap 12
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;
1 ignored issue
show
Bug Best Practice introduced by
The property endpoint does not exist on AlgoWeb\PODataLaravel\Models\MetadataTrait. Did you maybe forget to declare it?
Loading history...
135
136
        if (!isset($endpoint)) {
137
            $bitter = get_class($this);
138
            $name = substr($bitter, strrpos($bitter, '\\')+1);
139
            return ($name);
1 ignored issue
show
Bug Best Practice introduced by
The expression return $name could also return false which is incompatible with the documented return type null|string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
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(&$hooks, $first, $property, $last, $mult, $targ, $type = null, $through = null)
486
    {
487
        if (!isset($hooks[$first])) {
488
            $hooks[$first] = [];
489
        }
490
        if (!isset($hooks[$first][$targ])) {
491
            $hooks[$first][$targ] = [];
492
        }
493
        $hooks[$first][$targ][$property] = [
494
            'property' => $property,
495
            'local' => $last,
496
            'through' => $through,
497
            'multiplicity' => $mult,
498
            'type' => $type
499
        ];
500
    }
501
502
    /**
503
     * @param $rels
504
     * @param $hooks
505
     */
506
    private function getRelationshipsHasMany($rels, &$hooks)
507
    {
508
        foreach ($rels['HasMany'] as $property => $foo) {
509
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
510
                continue;
511
            }
512
            $mult = '*';
513
            $targ = get_class($foo->getRelated());
514
            list($thruName, $fkMethodName, $rkMethodName) = $this->getRelationsHasManyKeyNames($foo);
515
516
            $keyRaw = $foo->$fkMethodName();
517
            $keySegments = explode('.', $keyRaw);
518
            $keyName = $keySegments[count($keySegments)-1];
519
            $localRaw = $foo->$rkMethodName();
520
            $localSegments = explode('.', $localRaw);
521
            $localName = $localSegments[count($localSegments)-1];
522
            if (null !== $thruName) {
523
                $thruRaw = $foo->$thruName();
524
                $thruSegments = explode('.', $thruRaw);
525
                $thruName = $thruSegments[count($thruSegments)-1];
526
            }
527
            $first = $keyName;
528
            $last = $localName;
529
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
530
        }
531
    }
532
533
    /**
534
     * @param $rels
535
     * @param $hooks
536
     */
537
    private function getRelationshipsHasOne($rels, &$hooks)
538
    {
539
        foreach ($rels['HasOne'] as $property => $foo) {
540
            if ($foo instanceof MorphOne) {
541
                continue;
542
            }
543
            $isBelong = $foo instanceof BelongsTo;
544
            $mult = $isBelong ? '1' : '0..1';
545
            $targ = get_class($foo->getRelated());
546
547
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
548
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
549
550
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
551
            $keySegments = explode('.', $keyName);
552
            $keyName = $keySegments[count($keySegments)-1];
553
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
554
            $localSegments = explode('.', $localRaw);
555
            $localName = $localSegments[count($localSegments)-1];
556
            $first = $isBelong ? $localName : $keyName;
557
            $last = $isBelong ? $keyName : $localName;
558
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
559
        }
560
    }
561
562
    /**
563
     * @param $rels
564
     * @param $hooks
565
     */
566
    private function getRelationshipsKnownPolyMorph($rels, &$hooks)
567
    {
568
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
569
            $isMany = $foo instanceof MorphToMany;
570
            $targ = get_class($foo->getRelated());
571
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
572
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
573
574
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
575
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
576
577
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
578
            $keySegments = explode('.', $keyRaw);
579
            $keyName = $keySegments[count($keySegments)-1];
580
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
581
            $localSegments = explode('.', $localRaw);
582
            $localName = $localSegments[count($localSegments)-1];
583
            $first = $isMany ? $keyName : $localName;
584
            $last = $isMany ? $localName : $keyName;
585
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
586
        }
587
    }
588
589
    /**
590
     * @param $rels
591
     * @param $hooks
592
     */
593
    private function getRelationshipsUnknownPolyMorph($rels, &$hooks)
594
    {
595
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
596
            $isMany = $foo instanceof MorphToMany;
597
            $targ = get_class($foo->getRelated());
598
            $mult = $isMany ? '*' : '1';
599
600
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
601
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
602
603
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
604
            $keySegments = explode('.', $keyRaw);
605
            $keyName = $keySegments[count($keySegments)-1];
606
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
607
            $localSegments = explode('.', $localRaw);
608
            $localName = $localSegments[count($localSegments)-1];
609
610
            $first = $keyName;
611
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
612
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
613
        }
614
    }
615
616
    /**
617
     * SUpplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
618
     *
619
     * @return array
620
     */
621
    public function retrieveCasts()
622
    {
623
        if (method_exists($this, 'getCasts')) {
624
            return $this->getCasts();
625
        }
626
        return $this->casts;
1 ignored issue
show
Bug Best Practice introduced by
The property casts does not exist on AlgoWeb\PODataLaravel\Models\MetadataTrait. Did you maybe forget to declare it?
Loading history...
627
    }
628
629
    /**
630
     * Return list of relations to be eager-loaded by Laravel query provider.
631
     *
632
     * @return array
633
     */
634
    public function getEagerLoad()
635
    {
636
        assert(is_array($this->loadEagerRelations), 'LoadEagerRelations not an array');
637
        return $this->loadEagerRelations;
638
    }
639
640
    /**
641
     * Set list of relations to be eager-loaded.
642
     *
643
     * @param array $relations
644
     */
645
    public function setEagerLoad(array $relations)
646
    {
647
        $check = array_map('strval', $relations);
648
        assert($relations == $check, 'All supplied relations must be resolvable to strings');
649
        $this->loadEagerRelations = $relations;
650
    }
651
652
    /*
653
     * Is this model the known side of at least one polymorphic relation?
654
     */
655
    public function isKnownPolymorphSide()
656
    {
657
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
658
        // you're turned around
659
        $rels = $this->getRelationshipsFromMethods();
660
        return !empty($rels['KnownPolyMorphSide']);
661
    }
662
663
    /*
664
     * Is this model on the unknown side of at least one polymorphic relation?
665
     */
666
    public function isUnknownPolymorphSide()
667
    {
668
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
669
        // you're turned around
670
        $rels = $this->getRelationshipsFromMethods();
671
        return !empty($rels['UnknownPolyMorphSide']);
672
    }
673
674
    /**
675
     * Extract entity gubbins detail for later downstream use.
676
     *
677
     * @return EntityGubbins
678
     */
679
    public function extractGubbins()
680
    {
681
        $multArray = [
682
            '*' => AssociationStubRelationType::MANY(),
683
            '1' => AssociationStubRelationType::ONE(),
684
            '0..1' => AssociationStubRelationType::NULL_ONE()
685
        ];
686
687
        $gubbins = new EntityGubbins();
688
        $gubbins->setName($this->getEndpointName());
689
        $gubbins->setClassName(get_class($this));
690
691
        $lowerNames = [];
692
693
        $fields = $this->metadata();
694
        $entityFields = [];
695
        foreach ($fields as $name => $field) {
696
            if (in_array(strtolower($name), $lowerNames)) {
697
                $msg = 'Property names must be unique, without regard to case';
698
                throw new \Exception($msg);
699
            }
700
            $lowerNames[] = strtolower($name);
701
            $nuField = new EntityField();
702
            $nuField->setName($name);
703
            $nuField->setIsNullable($field['nullable']);
704
            $nuField->setReadOnly(false);
705
            $nuField->setCreateOnly(false);
706
            $nuField->setDefaultValue($field['default']);
707
            $nuField->setIsKeyField($this->getKeyName() == $name);
708
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
709
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
710
            $entityFields[$name] = $nuField;
711
        }
712
        $isEmpty = (0 === count($entityFields));
713
        if (!($isEmpty && $this->isRunningInArtisan())) {
714
            $gubbins->setFields($entityFields);
715
        }
716
717
        $rawRels = $this->getRelationships();
718
        $stubs = [];
719
        foreach ($rawRels as $key => $rel) {
720
            foreach ($rel as $rawName => $deets) {
721
                foreach ($deets as $relName => $relGubbins) {
722
                    if (in_array(strtolower($relName), $lowerNames)) {
723
                        $msg = 'Property names must be unique, without regard to case';
724
                        throw new \Exception($msg);
725
                    }
726
                    $lowerNames[] = strtolower($relName);
727
                    $gubbinsType = $relGubbins['type'];
728
                    $property = $relGubbins['property'];
729
                    $isPoly = isset($gubbinsType);
730
                    $targType = 'known' != $gubbinsType ? $rawName : null;
731
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
732
                    $stub->setBaseType(get_class($this));
733
                    $stub->setRelationName($property);
734
                    $stub->setKeyField($relGubbins['local']);
735
                    $stub->setForeignField($targType ? $key : null);
736
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
737
                    $stub->setTargType($targType);
738
                    if (null !== $relGubbins['through']) {
739
                        $stub->setThroughField($relGubbins['through']);
740
                    }
741
                    assert($stub->isOk(), 'Generated stub not consistent');
742
                    $stubs[$property] = $stub;
743
                }
744
            }
745
        }
746
        $gubbins->setStubs($stubs);
747
748
        return $gubbins;
749
    }
750
751
    public function synthLiteralPK()
752
    {
753
        if (!$this->isKnownPolymorphSide()) {
754
            return;
755
        }
756
        $fieldName = LaravelReadQuery::PK;
757
        /* As MetadataTrait is assumed to be deployed on top of an Eloquent model, which has getKey(), this is a
758
           false positive */
759
        /** @scrutinizer ignore-call */
760
        $this->$fieldName = $this->getKey();
761
    }
762
763
    public function isRunningInArtisan()
764
    {
765
        return App::runningInConsole() && !App::runningUnitTests();
766
    }
767
768
    protected function getTableColumns()
769
    {
770
        if (0 === count(self::$tableColumns)) {
771
            $table = $this->getTable();
772
            $connect = $this->getConnection();
773
            $builder = $connect->getSchemaBuilder();
774
            $columns = $builder->getColumnListing($table);
775
776
            self::$tableColumns = $columns;
777
        }
778
        return self::$tableColumns;
779
    }
780
781
    protected function getTableDoctrineColumns()
782
    {
783
        if (0 === count(self::$tableColumnsDoctrine)) {
784
            $table = $this->getTable();
785
            $connect = $this->getConnection();
786
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
787
788
            self::$tableColumnsDoctrine = $columns;
789
        }
790
        return self::$tableColumnsDoctrine;
791
    }
792
793
    public function reset()
794
    {
795
        self::$tableData = [];
796
        self::$tableColumnsDoctrine = [];
797
        self::$tableColumns = [];
798
    }
799
800
    private function getRelationsHasManyKeyNames($foo)
801
    {
802
        $thruName = null;
803
        if ($foo instanceof HasManyThrough) {
804
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
805
            $thruName = $this->polyglotThroughKeyMethodNames($foo);
806
            return array($thruName, $fkMethodName, $rkMethodName);
807
        } elseif ($foo instanceof BelongsToMany) {
808
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, true);
809
            return array($thruName, $fkMethodName, $rkMethodName);
810
        } else {
811
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodBackupNames($foo, true);
812
            return array($thruName, $fkMethodName, $rkMethodName);
813
        }
814
    }
815
}
816