Passed
Pull Request — master (#171)
by Alex
04:02
created

MetadataTrait::polyglotKeyMethodNames()   B

Complexity

Conditions 9
Paths 37

Size

Total Lines 41
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 41
ccs 0
cts 0
cp 0
rs 8.0555
c 0
b 0
f 0
cc 9
nc 37
nop 2
crap 90
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