Passed
Pull Request — master (#201)
by Alex
06:01
created

MetadataTrait::addRelationsHook()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 10
c 2
b 0
f 0
dl 0
loc 14
ccs 0
cts 0
cp 0
rs 9.9332
cc 3
nc 4
nop 8
crap 12

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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