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

MetadataTrait   F

Complexity

Total Complexity 112

Size/Duplication

Total Lines 742
Duplicated Lines 0 %

Test Coverage

Coverage 72.85%

Importance

Changes 24
Bugs 0 Features 0
Metric Value
wmc 112
eloc 334
c 24
b 0
f 0
dl 0
loc 742
ccs 110
cts 151
cp 0.7285
rs 2

23 Methods

Rating   Name   Duplication   Size   Complexity  
A metadataMask() 0 13 3
A getAllAttributes() 0 23 4
A getRelationships() 0 19 2
C metadata() 0 75 12
A getEndpointName() 0 10 3
A isKnownPolymorphSide() 0 6 1
A retrieveCasts() 0 4 2
A collectGetters() 0 19 6
A setEagerLoad() 0 3 1
B getRelationshipsKnownPolyMorph() 0 24 9
A getTableDoctrineColumns() 0 10 2
A reset() 0 5 1
C extractGubbins() 0 72 14
A getTableColumns() 0 11 2
D getRelationshipsFromMethods() 0 98 20
A isRunningInArtisan() 0 3 2
A getRelationshipsHasMany() 0 28 5
A getEagerLoad() 0 3 1
B getRelationshipsHasOne() 0 26 8
B getRelationshipsUnknownPolyMorph() 0 24 7
A addRelationsHook() 0 14 3
A isUnknownPolymorphSide() 0 6 1
A getClassMethods() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like MetadataTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MetadataTrait, and based on these observations, apply Extract Interface, too.

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