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

MetadataTrait::getRelationshipsFromMethods()   C

Complexity

Conditions 17
Paths 12

Size

Total Lines 78
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 17

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 55
c 8
b 0
f 0
dl 0
loc 78
ccs 2
cts 2
cp 1
rs 5.2166
cc 17
nc 12
nop 1
crap 17

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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