Completed
Pull Request — master (#141)
by Alex
02:08
created

MetadataTrait::getConnectionName()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 1
ccs 0
cts 0
cp 0
nc 1
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\MorphMany;
16
use Illuminate\Database\Eloquent\Relations\MorphOne;
17
use Illuminate\Database\Eloquent\Relations\MorphToMany;
18
use Illuminate\Database\Eloquent\Relations\Relation;
19
use Illuminate\Support\Facades\App;
20
21
trait MetadataTrait
22
{
23
    protected static $relationHooks = [];
24
    protected static $relationCategories = [];
25
    protected static $methodPrimary = [];
26
    protected static $methodAlternate = [];
27
    protected $loadEagerRelations = [];
28 3
    protected static $tableColumns = [];
29
    protected static $tableColumnsDoctrine = [];
30 3
    protected static $tableData = [];
31
    protected static $dontCastTypes = ['object', 'array', 'collection', 'int'];
32
33 2
    /*
34 2
     * Retrieve and assemble this model's metadata for OData packaging
35
     */
36 2
    public function metadata()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
37
    {
38 2
        assert($this instanceof Model, get_class($this));
39 1
40
        // Break these out separately to enable separate reuse
41
        $connect = $this->getConnection();
42 1
        $builder = $connect->getSchemaBuilder();
43 1
44 1
        $table = $this->getTable();
45
46 1
        if (!$builder->hasTable($table)) {
47
            return [];
48 1
        }
49 1
        if (0 !== count(self::$tableData)) {
50 1
            return self::$tableData;
51
        }
52 1
53 1
        $columns = $this->getTableColumns();
54 1
        $mask = $this->metadataMask();
55
        $columns = array_intersect($columns, $mask);
56 1
57
        $tableData = [];
58 1
59 1
        $rawFoo = $this->getTableDoctrineColumns();
60 1
        $foo = [];
61 1
        $getters = $this->collectGetters();
62 1
        $getters = array_intersect($getters, $mask);
63 1
        $casts = $this->getCasts();
64 1
65
        foreach ($rawFoo as $key => $val) {
66 1
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
67
            $key = trim($key, '`');
68
            $foo[$key] = $val;
69
        }
70
71
        foreach ($columns as $column) {
72
            // Doctrine schema manager returns columns with lowercased names
73 4
            $rawColumn = $foo[strtolower($column)];
74
            $nullable = !($rawColumn->getNotNull());
75 4
            $fillable = in_array($column, $this->getFillable());
76
            $rawType = $rawColumn->getType();
77 4
            $type = $rawType->getName();
78 4
            $default = $this->$column;
79 4
            $tableData[$column] = ['type' => $type,
80 2
                'nullable' => $nullable,
81 4
                'fillable' => $fillable,
82 1
                'default' => $default
83 1
            ];
84
        }
85 4
86
        foreach ($getters as $get) {
87
            if (isset($tableData[$get])) {
88
                continue;
89
            }
90
            $default = $this->$get;
91 5
            $tableData[$get] = ['type' => 'text', 'nullable' => true, 'fillable' => false, 'default' => $default];
92
        }
93 5
94 5
        // now, after everything's gathered up, apply Eloquent model's $cast array
95 1
        foreach ($casts as $key => $type) {
96
            $type = strtolower($type);
97
            if (array_key_exists($key, $tableData) && !in_array($type, self::$dontCastTypes)) {
98 4
                $tableData[$key]['type'] = $type;
99
            }
100 4
        }
101
102 4
        self::$tableData = $tableData;
103 4
        return $tableData;
104 4
    }
105 4
106 4
    /*
107 4
     * Return the set of fields that are permitted to be in metadata
108
     * - following same visible-trumps-hidden guideline as Laravel
109 4
     */
110 1
    public function metadataMask()
111 1
    {
112 1
        $attribs = array_keys($this->getAllAttributes());
113 1
114
        $visible = $this->getVisible();
115 4
        $hidden = $this->getHidden();
116 4
        if (0 < count($visible)) {
117
            assert(!empty($visible));
118 4
            $attribs = array_intersect($visible, $attribs);
119
        } elseif (0 < count($hidden)) {
120
            assert(!empty($hidden));
121
            $attribs = array_diff($attribs, $hidden);
122
        }
123
124
        return $attribs;
125
    }
126
127
    /*
128
     * Get the endpoint name being exposed
129
     *
130
     */
131
    public function getEndpointName()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
132
    {
133
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
134
135
        if (!isset($endpoint)) {
136
            $bitter = get_class();
137
            $name = substr($bitter, strrpos($bitter, '\\')+1);
138
            return ($name);
139
        }
140
        return ($endpoint);
141
    }
142
143
    /**
144
     * Get model's relationships.
145
     *
146
     * @return array
147
     */
148
    public function getRelationships()
149
    {
150
        if (empty(static::$relationHooks)) {
151
            $hooks = [];
152
153
            $rels = $this->getRelationshipsFromMethods(true);
154
155
            $this->getRelationshipsUnknownPolyMorph($rels, $hooks);
156
157
            $this->getRelationshipsKnownPolyMorph($rels, $hooks);
158
159
            $this->getRelationshipsHasOne($rels, $hooks);
160
161
            $this->getRelationshipsHasMany($rels, $hooks);
162
163
            static::$relationHooks = $hooks;
164
        }
165
166
        return static::$relationHooks;
167
    }
168
169
    protected function getAllAttributes()
170
    {
171
        // Adapted from http://stackoverflow.com/a/33514981
172
        // $columns = $this->getFillable();
173
        // Another option is to get all columns for the table like so:
174
        $columns = $this->getTableColumns();
175 3
        // but it's safer to just get the fillable fields
176
177 3
        $attributes = $this->getAttributes();
178
179 3
        foreach ($columns as $column) {
180 3
            if (!array_key_exists($column, $attributes)) {
181 3
                $attributes[$column] = null;
182 3
            }
183 3
        }
184 3
185 3
        $methods = $this->collectGetters();
186 3
187 3
        foreach ($methods as $method) {
188 3
            $attributes[$method] = null;
189
        }
190 3
191
        return $attributes;
192 3
    }
193 3
194 3
    /**
195 3
     * @param bool $biDir
196 3
     *
197 3
     * @return array
198 3
     */
199 3
    protected function getRelationshipsFromMethods($biDir = false)
200 3
    {
201 3
        $biDirVal = intval($biDir);
202
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
203 3
        if (!$isCached) {
204 3
            $model = $this;
205 3
            $relationships = [
206 3
                'HasOne' => [],
207 3
                'UnknownPolyMorphSide' => [],
208 3
                'HasMany' => [],
209 3
                'KnownPolyMorphSide' => []
210 3
            ];
211
            $methods = get_class_methods($model);
212 3
            if (!empty($methods)) {
213 3
                foreach ($methods as $method) {
214 3
                    if (!method_exists('Illuminate\Database\Eloquent\Model', $method)
215
                        && !method_exists(Mock::class, $method)
216 3
                        && !method_exists(MetadataTrait::class, $method)
217 3
                    ) {
218 3
                        //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
219 3
                        $reflection = new \ReflectionMethod($model, $method);
220 3
                        $fileName = $reflection->getFileName();
221
222 1
                        $file = new \SplFileObject($fileName);
223 3
                        $file->seek($reflection->getStartLine()-1);
224
                        $code = '';
225 1
                        while ($file->key() < $reflection->getEndLine()) {
226
                            $code .= $file->current();
227 1
                            $file->next();
228
                        }
229 1
230
                        $code = trim(preg_replace('/\s\s+/', '', $code));
231 3
                        assert(
232 2
                            false !== stripos($code, 'function'),
233 2
                            'Function definition must have keyword \'function\''
234 3
                        );
235 3
                        $begin = strpos($code, 'function(');
236 3
                        $code = substr($code, $begin, strrpos($code, '}')-$begin+1);
237 3
                        $lastCode = $code[strlen($code)-1];
238 3
                        assert('}' == $lastCode, 'Final character of function definition must be closing brace');
239 3
                        foreach ([
240 3
                                     'hasMany',
241
                                     'hasManyThrough',
242
                                     'belongsToMany',
243
                                     'hasOne',
244
                                     'belongsTo',
245
                                     'morphOne',
246
                                     'morphTo',
247
                                     'morphMany',
248
                                     'morphToMany',
249
                                     'morphedByMany'
250
                                 ] as $relation) {
251
                            $search = '$this->' . $relation . '(';
252
                            if ($pos = stripos($code, $search)) {
253
                                //Resolve the relation's model to a Relation object.
254
                                $relationObj = $model->$method();
255
                                if ($relationObj instanceof Relation) {
256
                                    $relObject = $relationObj->getRelated();
257
                                    $relatedModel = '\\' . get_class($relObject);
258
                                    if (in_array(MetadataTrait::class, class_uses($relatedModel))) {
259
                                        $relations = [
260
                                            'hasManyThrough',
261
                                            'belongsToMany',
262
                                            'hasMany',
263
                                            'morphMany',
264
                                            'morphToMany',
265
                                            'morphedByMany'
266
                                        ];
267
                                        if (in_array($relation, $relations)) {
268
                                            //Collection or array of models (because Collection is Arrayable)
269
                                            $relationships['HasMany'][$method] = $biDir ? $relationObj : $relatedModel;
270
                                        } elseif ('morphTo' === $relation) {
271
                                            // Model isn't specified because relation is polymorphic
272
                                            $relationships['UnknownPolyMorphSide'][$method] =
273
                                                $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
274
                                        } else {
275
                                            //Single model is returned
276
                                            $relationships['HasOne'][$method] = $biDir ? $relationObj : $relatedModel;
277
                                        }
278
                                        if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
279
                                            $relationships['KnownPolyMorphSide'][$method] =
280
                                                $biDir ? $relationObj : $relatedModel;
281
                                        }
282
                                        if (in_array($relation, ['morphedByMany'])) {
283
                                            $relationships['UnknownPolyMorphSide'][$method] =
284
                                                $biDir ? $relationObj : $relatedModel;
285
                                        }
286
                                    }
287
                                }
288
                            }
289
                        }
290
                    }
291
                }
292
            }
293
            static::$relationCategories[$biDirVal] = $relationships;
294
        }
295
        return static::$relationCategories[$biDirVal];
296
    }
297
298
    /**
299
     * Get the visible attributes for the model.
300
     *
301
     * @return array
302
     */
303
    abstract public function getVisible();
304
305
    /**
306
     * Get the hidden attributes for the model.
307
     *
308
     * @return array
309
     */
310
    abstract public function getHidden();
311
312
    /**
313
     * Get the primary key for the model.
314
     *
315
     * @return string
316
     */
317
    abstract public function getKeyName();
318
319
    /**
320
     * Get the current connection name for the model.
321
     *
322
     * @return string
323
     */
324
    abstract public function getConnectionName();
325
326
    /**
327
     * Get the database connection for the model.
328
     *
329
     * @return \Illuminate\Database\Connection
330
     */
331
    abstract public function getConnection();
332
333
    /**
334
     * Get all of the current attributes on the model.
335
     *
336
     * @return array
337
     */
338
    abstract public function getAttributes();
339
340
    /**
341
     * Get the table associated with the model.
342
     *
343
     * @return string
344
     */
345
    abstract public function getTable();
346
347
    /**
348
     * Get the fillable attributes for the model.
349
     *
350
     * @return array
351
     */
352
    abstract public function getFillable();
353
354
    /**
355
     * Dig up all defined getters on the model.
356
     *
357
     * @return array
358
     */
359
    protected function collectGetters()
360
    {
361
        $getterz = [];
362
        $methods = get_class_methods($this);
363
        foreach ($methods as $method) {
364
            if (12 < strlen($method) && 'get' == substr($method, 0, 3)) {
365
                if ('Attribute' == substr($method, -9)) {
366
                    $getterz[] = $method;
367
                }
368
            }
369
        }
370
        $methods = [];
371
372
        foreach ($getterz as $getter) {
373
            $residual = substr($getter, 3);
374
            $residual = substr($residual, 0, -9);
375
            $methods[] = $residual;
376
        }
377
        return $methods;
378
    }
379
380
    /**
381
     * @param       $foo
382
     * @param mixed $condition
383
     *
384
     * @return array
385
     */
386
    private function polyglotKeyMethodNames($foo, $condition = false)
387
    {
388
        $fkList = ['getQualifiedForeignKeyName', 'getForeignKey'];
389
        $rkList = ['getQualifiedRelatedKeyName', 'getOtherKey', 'getOwnerKey'];
390
391
        $fkMethodName = null;
392
        $rkMethodName = null;
393
        if ($condition) {
394
            if (array_key_exists(get_class($foo), static::$methodPrimary)) {
395
                $line = static::$methodPrimary[get_class($foo)];
396
                $fkMethodName = $line['fk'];
397
                $rkMethodName = $line['rk'];
398
            } else {
399
                $methodList = get_class_methods(get_class($foo));
400
                $fkMethodName = 'getQualifiedForeignPivotKeyName';
401
                foreach ($fkList as $option) {
402
                    if (in_array($option, $methodList)) {
403
                        $fkMethodName = $option;
404
                        break;
405
                    }
406
                }
407
                assert(
408
                    in_array($fkMethodName, $methodList),
409
                    'Selected method, ' . $fkMethodName . ', not in method list'
410
                );
411
                $rkMethodName = 'getQualifiedRelatedPivotKeyName';
412
                foreach ($rkList as $option) {
413
                    if (in_array($option, $methodList)) {
414
                        $rkMethodName = $option;
415
                        break;
416
                    }
417
                }
418
                assert(
419
                    in_array($rkMethodName, $methodList),
420
                    'Selected method, ' . $rkMethodName . ', not in method list'
421
                );
422
                $line = ['fk' => $fkMethodName, 'rk' => $rkMethodName];
423
                static::$methodPrimary[get_class($foo)] = $line;
424
            }
425
        }
426
        return [$fkMethodName, $rkMethodName];
427
    }
428
429
    private function polyglotKeyMethodBackupNames($foo, $condition = false)
430
    {
431
        $fkList = ['getForeignKey', 'getForeignKeyName'];
432
        $rkList = ['getOtherKey', 'getQualifiedParentKeyName'];
433
434
        $fkMethodName = null;
435
        $rkMethodName = null;
436
        if ($condition) {
437
            if (array_key_exists(get_class($foo), static::$methodAlternate)) {
438
                $line = static::$methodAlternate[get_class($foo)];
439
                $fkMethodName = $line['fk'];
440
                $rkMethodName = $line['rk'];
441
            } else {
442
                $methodList = get_class_methods(get_class($foo));
443
                $fkCombo = array_values(array_intersect($fkList, $methodList));
444
                assert(1 <= count($fkCombo), 'Expected at least 1 element in foreign-key list, got ' . count($fkCombo));
445
                $fkMethodName = $fkCombo[0];
446
                assert(
447
                    in_array($fkMethodName, $methodList),
448
                    'Selected method, ' . $fkMethodName . ', not in method list'
449
                );
450
                $rkCombo = array_values(array_intersect($rkList, $methodList));
451
                assert(1 <= count($rkCombo), 'Expected at least 1 element in related-key list, got ' . count($rkCombo));
452
                $rkMethodName = $rkCombo[0];
453
                assert(
454
                    in_array($rkMethodName, $methodList),
455
                    'Selected method, ' . $rkMethodName . ', not in method list'
456
                );
457
                $line = ['fk' => $fkMethodName, 'rk' => $rkMethodName];
458
                static::$methodAlternate[get_class($foo)] = $line;
459
            }
460
        }
461
        return [$fkMethodName, $rkMethodName];
462
    }
463
464
    /**
465
     * @param             $hooks
466
     * @param             $first
467
     * @param             $property
468
     * @param             $last
469
     * @param             $mult
470
     * @param             $targ
471
     * @param string|null $targ
472
     * @param null|mixed  $type
473
     */
474
    private function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ, $type = null)
475
    {
476
        if (!isset($hooks[$first])) {
477
            $hooks[$first] = [];
478
        }
479
        if (!isset($hooks[$first][$targ])) {
480
            $hooks[$first][$targ] = [];
481
        }
482
        $hooks[$first][$targ][$property] = [
483
            'property' => $property,
484
            'local' => $last,
485
            'multiplicity' => $mult,
486
            'type' => $type
487
        ];
488
    }
489
490
    /**
491
     * @param $rels
492
     * @param $hooks
493
     */
494
    private function getRelationshipsHasMany($rels, &$hooks)
495
    {
496
        foreach ($rels['HasMany'] as $property => $foo) {
497
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
498
                continue;
499
            }
500
            $isBelong = $foo instanceof BelongsToMany;
501
            $mult = '*';
502
            $targ = get_class($foo->getRelated());
503
504
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
505
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
506
507
            $keyRaw = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
508
            $keySegments = explode('.', $keyRaw);
509
            $keyName = $keySegments[count($keySegments)-1];
510
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
511
            $localSegments = explode('.', $localRaw);
512
            $localName = $localSegments[count($localSegments)-1];
513
            $first = $keyName;
514
            $last = $localName;
515
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
516
        }
517
    }
518
519
    /**
520
     * @param $rels
521
     * @param $hooks
522
     */
523
    private function getRelationshipsHasOne($rels, &$hooks)
524
    {
525
        foreach ($rels['HasOne'] as $property => $foo) {
526
            if ($foo instanceof MorphOne) {
527
                continue;
528
            }
529
            $isBelong = $foo instanceof BelongsTo;
530
            $mult = $isBelong ? '1' : '0..1';
531
            $targ = get_class($foo->getRelated());
532
533
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
534
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
535
536
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
537
            $keySegments = explode('.', $keyName);
538
            $keyName = $keySegments[count($keySegments)-1];
539
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
540
            $localSegments = explode('.', $localRaw);
541
            $localName = $localSegments[count($localSegments)-1];
542
            $first = $isBelong ? $localName : $keyName;
543
            $last = $isBelong ? $keyName : $localName;
544
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
545
        }
546
    }
547
548
    /**
549
     * @param $rels
550
     * @param $hooks
551
     */
552
    private function getRelationshipsKnownPolyMorph($rels, &$hooks)
553
    {
554
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
555
            $isMany = $foo instanceof MorphToMany;
556
            $targ = get_class($foo->getRelated());
557
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
558
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
559
560
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
561
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
562
563
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
564
            $keySegments = explode('.', $keyRaw);
565
            $keyName = $keySegments[count($keySegments)-1];
566
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
567
            $localSegments = explode('.', $localRaw);
568
            $localName = $localSegments[count($localSegments)-1];
569
            $first = $isMany ? $keyName : $localName;
570
            $last = $isMany ? $localName : $keyName;
571
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
572
        }
573
    }
574
575
    /**
576
     * @param $rels
577
     * @param $hooks
578
     */
579
    private function getRelationshipsUnknownPolyMorph($rels, &$hooks)
580
    {
581
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
582
            $isMany = $foo instanceof MorphToMany;
583
            $targ = get_class($foo->getRelated());
584
            $mult = $isMany ? '*' : '1';
585
586
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
587
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
588
589
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
590
            $keySegments = explode('.', $keyRaw);
591
            $keyName = $keySegments[count($keySegments)-1];
592
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
593
            $localSegments = explode('.', $localRaw);
594
            $localName = $localSegments[count($localSegments)-1];
595
596
            $first = $keyName;
597
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
598
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
599
        }
600
    }
601
602
    /**
603
     * SUpplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
604
     *
605
     * @return array
606
     */
607
    public function retrieveCasts()
608
    {
609
        return $this->casts;
610
    }
611
612
    /**
613
     * Return list of relations to be eager-loaded by Laravel query provider.
614
     *
615
     * @return array
616
     */
617
    public function getEagerLoad()
618
    {
619
        assert(is_array($this->loadEagerRelations));
620
        return $this->loadEagerRelations;
621
    }
622
623
    /**
624
     * Set list of relations to be eager-loaded.
625
     *
626
     * @param array $relations
627
     */
628
    public function setEagerLoad(array $relations)
629
    {
630
        $check = array_map('strval', $relations);
631
        assert($relations == $check, 'All supplied relations must be resolvable to strings');
632
        $this->loadEagerRelations = $relations;
633
    }
634
635
    /*
636
     * Is this model the known side of at least one polymorphic relation?
637
     */
638
    public function isKnownPolymorphSide()
639
    {
640
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
641
        // you're turned around
642
        $rels = $this->getRelationshipsFromMethods();
643
        return !empty($rels['KnownPolyMorphSide']);
644
    }
645
646
    /*
647
     * Is this model on the unknown side of at least one polymorphic relation?
648
     */
649
    public function isUnknownPolymorphSide()
650
    {
651
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
652
        // you're turned around
653
        $rels = $this->getRelationshipsFromMethods();
654
        return !empty($rels['UnknownPolyMorphSide']);
655
    }
656
657
    /**
658
     * Extract entity gubbins detail for later downstream use.
659
     *
660
     * @return EntityGubbins
661
     */
662
    public function extractGubbins()
663
    {
664
        $multArray = [
665
            '*' => AssociationStubRelationType::MANY(),
666
            '1' => AssociationStubRelationType::ONE(),
667
            '0..1' => AssociationStubRelationType::NULL_ONE()
668
        ];
669
670
        $gubbins = new EntityGubbins();
671
        $gubbins->setName($this->getEndpointName());
672
        $gubbins->setClassName(get_class($this));
673
674
        $lowerNames = [];
675
676
        $fields = $this->metadata();
677
        $entityFields = [];
678
        foreach ($fields as $name => $field) {
679
            if (in_array(strtolower($name), $lowerNames)) {
680
                $msg = 'Property names must be unique, without regard to case';
681
                throw new \Exception($msg);
682
            }
683
            $lowerNames[] = strtolower($name);
684
            $nuField = new EntityField();
685
            $nuField->setName($name);
686
            $nuField->setIsNullable($field['nullable']);
687
            $nuField->setReadOnly(false);
688
            $nuField->setCreateOnly(false);
689
            $nuField->setDefaultValue($field['default']);
690
            $nuField->setIsKeyField($this->getKeyName() == $name);
691
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
692
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
693
            $entityFields[$name] = $nuField;
694
        }
695
        $isEmpty = (0 === count($entityFields));
696
        if (!($isEmpty && $this->isRunningInArtisan())) {
697
            $gubbins->setFields($entityFields);
698
        }
699
700
        $rawRels = $this->getRelationships();
701
        $stubs = [];
702
        foreach ($rawRels as $key => $rel) {
703
            foreach ($rel as $rawName => $deets) {
704
                foreach ($deets as $relName => $relGubbins) {
705
                    if (in_array(strtolower($relName), $lowerNames)) {
706
                        $msg = 'Property names must be unique, without regard to case';
707
                        throw new \Exception($msg);
708
                    }
709
                    $lowerNames[] = strtolower($relName);
710
                    $gubbinsType = $relGubbins['type'];
711
                    $property = $relGubbins['property'];
712
                    $isPoly = isset($gubbinsType);
713
                    $targType = 'known' != $gubbinsType ? $rawName : null;
714
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
715
                    $stub->setBaseType(get_class($this));
716
                    $stub->setRelationName($property);
717
                    $stub->setKeyField($relGubbins['local']);
718
                    $stub->setForeignField($targType ? $key : null);
719
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
720
                    $stub->setTargType($targType);
721
                    assert($stub->isOk());
722
                    $stubs[$property] = $stub;
723
                }
724
            }
725
        }
726
        $gubbins->setStubs($stubs);
727
728
        return $gubbins;
729
    }
730
731
    public function synthLiteralPK()
732
    {
733
        if (!$this->isKnownPolymorphSide()) {
734
            return;
735
        }
736
        $fieldName = LaravelReadQuery::PK;
737
        $this->$fieldName = $this->getKey();
738
    }
739
740
    public function isRunningInArtisan()
741
    {
742
        return App::runningInConsole() && !App::runningUnitTests();
743
    }
744
745
    protected function getTableColumns()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
746
    {
747
        if (0 === count(self::$tableColumns)) {
748
            $table = $this->getTable();
749
            $connect = $this->getConnection();
750
            $builder = $connect->getSchemaBuilder();
751
            $columns = $builder->getColumnListing($table);
752
753
            self::$tableColumns = $columns;
754
        }
755
        return self::$tableColumns;
756
    }
757
758
    protected function getTableDoctrineColumns()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
759
    {
760
        if (0 === count(self::$tableColumnsDoctrine)) {
761
            $table = $this->getTable();
762
            $connect = $this->getConnection();
763
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
764
765
            self::$tableColumnsDoctrine = $columns;
766
        }
767
        return self::$tableColumnsDoctrine;
768
    }
769
770
    public function reset()
771
    {
772
        self::$tableData = [];
773
        self::$tableColumnsDoctrine = [];
774
        self::$tableColumns = [];
775
    }
776
}
777