Completed
Pull Request — master (#219)
by Christopher
17:20 queued 05:52
created

MetadataRelationsTrait   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 47
eloc 125
c 4
b 0
f 0
dl 0
loc 289
rs 8.64

9 Methods

Rating   Name   Duplication   Size   Complexity  
A isUnknownPolymorphSide() 0 6 1
C getRelationshipsFromMethods() 0 78 17
A getRelationships() 0 19 2
A isKnownPolymorphSide() 0 6 1
A addRelationsHook() 0 22 3
A getRelationshipsHasOne() 0 19 6
B getRelationshipsKnownPolyMorph() 0 17 7
A getRelationshipsHasMany() 0 21 5
A getRelationshipsUnknownPolyMorph() 0 17 5

How to fix   Complexity   

Complex Class

Complex classes like MetadataRelationsTrait 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 MetadataRelationsTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: alex
5
 * Date: 13/02/20
6
 * Time: 1:08 PM.
7
 */
8
namespace AlgoWeb\PODataLaravel\Models;
9
10
use Illuminate\Database\Eloquent\Model;
11
use Illuminate\Database\Eloquent\Relations\BelongsTo;
12
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
13
use Illuminate\Database\Eloquent\Relations\MorphMany;
14
use Illuminate\Database\Eloquent\Relations\MorphOne;
15
use Illuminate\Database\Eloquent\Relations\MorphToMany;
16
use Illuminate\Database\Eloquent\Relations\Relation;
17
use POData\Common\InvalidOperationException;
18
19
trait MetadataRelationsTrait
20
{
21
    use MetadataKeyMethodNamesTrait;
22
23
    protected static $relationHooks = [];
24
    protected static $relationCategories = [];
25
26
27
    /**
28
     * Get model's relationships.
29
     *
30
     * @throws InvalidOperationException
31
     * @throws \ReflectionException
32
     * @return array
33
     */
34
    public function getRelationships()
35
    {
36
        if (empty(static::$relationHooks)) {
37
            $hooks = [];
38
39
            $rels = $this->getRelationshipsFromMethods(true);
40
41
            $this->getRelationshipsUnknownPolyMorph($rels, $hooks);
42
43
            $this->getRelationshipsKnownPolyMorph($rels, $hooks);
44
45
            $this->getRelationshipsHasOne($rels, $hooks);
46
47
            $this->getRelationshipsHasMany($rels, $hooks);
48
49
            static::$relationHooks = $hooks;
50
        }
51
52
        return static::$relationHooks;
53
    }
54
55
    /**
56
     * Is this model the known side of at least one polymorphic relation?
57
     *
58
     * @throws InvalidOperationException
59
     * @throws \ReflectionException
60
     */
61
    public function isKnownPolymorphSide()
62
    {
63
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
64
        // you're turned around
65
        $rels = $this->getRelationshipsFromMethods();
66
        return !empty($rels['KnownPolyMorphSide']);
67
    }
68
69
    /**
70
     * Is this model on the unknown side of at least one polymorphic relation?
71
     *
72
     * @throws InvalidOperationException
73
     * @throws \ReflectionException
74
     */
75
    public function isUnknownPolymorphSide()
76
    {
77
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
78
        // you're turned around
79
        $rels = $this->getRelationshipsFromMethods();
80
        return !empty($rels['UnknownPolyMorphSide']);
81
    }
82
83
    /**
84
     * @param bool $biDir
85
     *
86
     * @throws InvalidOperationException
87
     * @throws \ReflectionException
88
     * @return array
89
     */
90
    protected function getRelationshipsFromMethods(bool $biDir = false)
91
    {
92
        $biDirVal = intval($biDir);
93
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
94
        if (!$isCached) {
95
            /** @var Model $model */
96
            $model = $this;
97
            $relationships = [
98
                'HasOne' => [],
99
                'UnknownPolyMorphSide' => [],
100
                'HasMany' => [],
101
                'KnownPolyMorphSide' => []
102
            ];
103
            $methods = $this->getModelClassMethods($model);
104
            foreach ($methods as $method) {
105
                //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
106
                $reflection = new \ReflectionMethod($model, $method);
107
                $fileName = $reflection->getFileName();
108
109
                $file = new \SplFileObject($fileName);
110
                $file->seek($reflection->getStartLine()-1);
111
                $code = '';
112
                while ($file->key() < $reflection->getEndLine()) {
113
                    $code .= $file->current();
114
                    $file->next();
115
                }
116
117
                $code = trim(preg_replace('/\s\s+/', '', $code));
118
                if (false === stripos($code, 'function')) {
119
                    $msg = 'Function definition must have keyword \'function\'';
120
                    throw new InvalidOperationException($msg);
121
                }
122
                $begin = strpos($code, 'function(');
123
                $code = substr($code, /* @scrutinizer ignore-type */$begin, strrpos($code, '}')-$begin+1);
124
                $lastCode = $code[strlen(/* @scrutinizer ignore-type */$code)-1];
125
                if ('}' != $lastCode) {
126
                    $msg = 'Final character of function definition must be closing brace';
127
                    throw new InvalidOperationException($msg);
128
                }
129
                foreach (static::$relTypes as $relation) {
130
                    $search = '$this->' . $relation . '(';
131
                    $found = stripos(/* @scrutinizer ignore-type */$code, $search);
132
                    if (!$found) {
133
                        continue;
134
                    }
135
                    //Resolve the relation's model to a Relation object.
136
                    $relationObj = $model->$method();
137
                    if (!($relationObj instanceof Relation)) {
138
                        continue;
139
                    }
140
                    $relObject = $relationObj->getRelated();
141
                    $relatedModel = '\\' . get_class($relObject);
142
                    if (!in_array(MetadataTrait::class, class_uses($relatedModel))) {
143
                        continue;
144
                    }
145
                    $targObject = $biDir ? $relationObj : $relatedModel;
146
                    if (in_array($relation, static::$manyRelTypes)) {
147
                        //Collection or array of models (because Collection is Arrayable)
148
                        $relationships['HasMany'][$method] = $targObject;
149
                    } elseif ('morphTo' === $relation) {
150
                        // Model isn't specified because relation is polymorphic
151
                        $relationships['UnknownPolyMorphSide'][$method] =
152
                            $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
153
                    } else {
154
                        //Single model is returned
155
                        $relationships['HasOne'][$method] = $targObject;
156
                    }
157
                    if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
158
                        $relationships['KnownPolyMorphSide'][$method] = $targObject;
159
                    }
160
                    if (in_array($relation, ['morphedByMany'])) {
161
                        $relationships['UnknownPolyMorphSide'][$method] = $targObject;
162
                    }
163
                }
164
            }
165
            static::$relationCategories[$biDirVal] = $relationships;
166
        }
167
        return static::$relationCategories[$biDirVal];
168
    }
169
170
    /**
171
     * @param  array                     $rels
172
     * @param  array                     $hooks
173
     * @throws InvalidOperationException
174
     */
175
    protected function getRelationshipsHasMany(array $rels, array &$hooks)
176
    {
177
        /**
178
         * @var string   $property
179
         * @var Relation $relation
180
         */
181
        foreach ($rels['HasMany'] as $property => $relation) {
182
            if ($relation instanceof MorphMany || $relation instanceof MorphToMany) {
183
                continue;
184
            }
185
            $mult = '*';
186
            $targ = get_class($relation->getRelated());
187
            $keyName = $this->polyglotFkKey($relation);
188
            $localName = $this->polyglotRkKey($relation);
189
            $thruName = $relation instanceof HasManyThrough ?
190
                $this->polyglotThroughKey($relation) :
191
                null;
192
193
            $first = $keyName;
194
            $last = $localName;
195
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
196
        }
197
    }
198
199
    /**
200
     * @param  array                     $rels
201
     * @param  array                     $hooks
202
     * @throws InvalidOperationException
203
     */
204
    protected function getRelationshipsHasOne(array $rels, array &$hooks)
205
    {
206
        /**
207
         * @var string   $property
208
         * @var Relation $foo
209
         */
210
        foreach ($rels['HasOne'] as $property => $foo) {
211
            if ($foo instanceof MorphOne) {
212
                continue;
213
            }
214
            $isBelong = $foo instanceof BelongsTo;
215
            $mult = $isBelong ? '1' : '0..1';
216
            $targ = get_class($foo->getRelated());
217
218
            $keyName = $this->polyglotFkKey($foo);
219
            $localName = $this->polyglotRkKey($foo);
220
            $first = $isBelong ? $localName : $keyName;
221
            $last = $isBelong ? $keyName : $localName;
222
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
223
        }
224
    }
225
226
    /**
227
     * @param  array                     $rels
228
     * @param  array                     $hooks
229
     * @throws InvalidOperationException
230
     */
231
    protected function getRelationshipsKnownPolyMorph(array $rels, array &$hooks)
232
    {
233
        /**
234
         * @var string   $property
235
         * @var Relation $foo
236
         */
237
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
238
            $isMany = $foo instanceof MorphToMany;
239
            $targ = get_class($foo->getRelated());
240
            $mult = $isMany || $foo instanceof MorphMany ? '*' : '1';
241
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
242
243
            $keyName = $this->polyglotFkKey($foo);
244
            $localName = $this->polyglotRkKey($foo);
245
            $first = $isMany ? $keyName : $localName;
246
            $last = $isMany ? $localName : $keyName;
247
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
248
        }
249
    }
250
251
    /**
252
     * @param  array                     $rels
253
     * @param  array                     $hooks
254
     * @throws InvalidOperationException
255
     */
256
    protected function getRelationshipsUnknownPolyMorph(array $rels, array &$hooks)
257
    {
258
        /**
259
         * @var string   $property
260
         * @var Relation $foo
261
         */
262
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
263
            $isMany = $foo instanceof MorphToMany;
264
            $targ = get_class($foo->getRelated());
265
            $mult = $isMany ? '*' : '1';
266
267
            $keyName = $this->polyglotFkKey($foo);
268
            $localName = $this->polyglotRkKey($foo);
269
270
            $first = $keyName;
271
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
272
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
273
        }
274
    }
275
276
    /**
277
     * @param             $hooks
278
     * @param             $foreignField
279
     * @param             $RelationName
280
     * @param             $localKey
281
     * @param             $mult
282
     * @param string|null $relatedObject
283
     * @param mixed|null  $type
284
     * @param mixed|null  $through
285
     */
286
    protected function addRelationsHook(
287
        array &$hooks,
288
        $foreignField,
289
        $RelationName,
290
        $localKey,
291
        $mult,
292
        $relatedObject,
293
        $type = null,
294
        $through = null
295
    ) {
296
        if (!isset($hooks[$foreignField])) {
297
            $hooks[$foreignField] = [];
298
        }
299
        if (!isset($hooks[$foreignField][$relatedObject])) {
300
            $hooks[$foreignField][$relatedObject] = [];
301
        }
302
        $hooks[$foreignField][$relatedObject][$RelationName] = [
303
            'property' => $RelationName,
304
            'local' => $localKey,
305
            'through' => $through,
306
            'multiplicity' => $mult,
307
            'type' => $type
308
        ];
309
    }
310
}
311