Test Failed
Pull Request — master (#107)
by Alex
07:47
created

MetadataRelationHolder   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 256
Duplicated Lines 6.25 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 0
dl 16
loc 256
rs 8.3157
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A addModel() 0 13 3
D getRelationsByKey() 0 86 18
A getRelationsByClass() 8 18 4
A getRelations() 8 15 4
A hasClass() 0 4 1
D getMorphToRelations() 0 41 9
B calculateRoundTripRelationsGenForwardReverse() 0 38 1
A checkClassExists() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MetadataRelationHolder 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 MetadataRelationHolder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Models;
4
5
use Illuminate\Database\Eloquent\Model;
6
7
class MetadataRelationHolder
8
{
9
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
10
    protected $relations = [];
11
12
    public function __construct()
13
    {
14
    }
15
16
    /**
17
     * Add model's relationships to holder
18
     *
19
     * @param Model $model
20
     */
21
    public function addModel(Model $model)
22
    {
23
        if (!in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($model))) {
24
            $msg = 'Supplied model does not use MetadataTrait';
25
            throw new \InvalidArgumentException($msg);
26
        }
27
        $className = get_class($model);
28
        if (array_key_exists($className, $this->relations)) {
29
            $msg = $className.' already added';
30
            throw new \InvalidArgumentException($msg);
31
        }
32
        $this->relations[$className] = $model->getRelationships();
33
    }
34
35
    public function getRelationsByKey($className, $keyName)
36
    {
37
        $this->checkClassExists($className);
38
39
        $rels = $this->relations[$className];
40
        if (!array_key_exists($keyName, $rels)) {
41
            $msg = 'Key ' . $keyName . ' not registered on ' . $className;
42
            throw new \InvalidArgumentException($msg);
43
        }
44
45
        $result = [];
46
        $payload = $rels[$keyName];
47
        $principalType = $className;
48
        $numRel = 0;
49
        $isKnown = false;
50
51
        foreach ($payload as $dependentType => $targDeets) {
52
            if (!array_key_exists($dependentType, $this->relations)) {
53
                continue;
54
            }
55
            // if principal and ostensible dependent type are equal, drop through to specific handler
56
            // at moment, this is only for morphTo relations - morphedByMany doesn't cause this
57
            if ($principalType === $dependentType) {
58
                $morphToLines = $this->getMorphToRelations($principalType, $targDeets, $keyName);
59
                foreach ($morphToLines as $morph) {
60
                    if (!in_array($morph, $result)) {
61
                        $result[] = $morph;
62
                    }
63
                }
64
                $isKnown = true;
65
                continue;
66
            }
67
68
            $foreign = $this->relations[$dependentType];
69
70
            foreach ($targDeets as $principalProperty => $rawDeets) {
71
                $targKey = $rawDeets['local'];
72
                $principalMult = $rawDeets['multiplicity'];
73
                $principalProperty = $rawDeets['property'];
74
                if (!array_key_exists($targKey, $foreign)) {
75
                    continue;
76
                }
77
                $numRel++;
78
79
                $foreignDeets = $foreign[$targKey];
80
                foreach ($foreignDeets as $foreignType => $raw) {
81
                    if (!array_key_exists($foreignType, $this->relations)) {
82
                        continue;
83
                    }
84
                    foreach ($raw as $dependentProperty => $dependentPayload) {
85
                        if ($principalType !== $foreignType) {
86
                            if (null === $dependentPayload['type']) {
87
                                continue;
88
                            }
89
                        }
90
                        if ($keyName == $dependentPayload['local']) {
91
                            $dependentMult = $dependentPayload['multiplicity'];
92
                            // generate forward and reverse relations
93
                            list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
94
                                $principalType,
95
                                $principalMult,
96
                                $principalProperty,
97
                                $dependentType,
98
                                $dependentMult,
99
                                $dependentProperty
100
                            );
101
                            if (!in_array($forward, $result)) {
102
                                // add forward relation
103
                                $result[] = $forward;
104
                            }
105
                            if (!in_array($reverse, $result)) {
106
                                // add reverse relation
107
                                $result[] = $reverse;
108
                            }
109
                        }
110
                    }
111
                }
112
            }
113
        }
114
115
        $maxRel = $isKnown ? PHP_INT_MAX : 2 * $numRel;
116
        $msg = 'Key '.$keyName. ' on class '.$className . ' should have no more than '
117
               .$maxRel.' lines, has '.count($result);
118
        assert($maxRel >= count($result), $msg);
119
        return $result;
120
    }
121
122
    public function getRelationsByClass($className)
123
    {
124
        $this->checkClassExists($className);
125
126
        $rels = $this->relations[$className];
127
        $keys = array_keys($rels);
128
        $results = [];
129 View Code Duplication
        foreach ($keys as $key) {
130
            $lines = $this->getRelationsByKey($className, $key);
131
            foreach ($lines as $line) {
132
                if (!in_array($line, $results)) {
133
                    $results[] = $line;
134
                }
135
            }
136
        }
137
138
        return $results;
139
    }
140
141
    public function getRelations()
142
    {
143
        $results = [];
144
        $registered = array_keys($this->relations);
145
146 View Code Duplication
        foreach ($registered as $className) {
147
            $lines = $this->getRelationsByClass($className);
148
            foreach ($lines as $line) {
149
                if (!in_array($line, $results)) {
150
                    $results[] = $line;
151
                }
152
            }
153
        }
154
        return $results;
155
    }
156
157
    public function hasClass($className)
158
    {
159
        return array_key_exists($className, $this->relations);
160
    }
161
162
    private function getMorphToRelations($principalType, $targDeets, $keyName)
163
    {
164
        $result = [];
165
        $deetKeys = array_keys($targDeets);
166
        $principalProperty = $deetKeys[0];
167
        $principalDeets = $targDeets[$principalProperty];
168
        $principalMult = $principalDeets['multiplicity'];
169
170
        foreach ($this->relations as $dependentType => $dependentDeets) {
171
            foreach ($dependentDeets as $targKey => $rawDeets) {
172
                foreach ($rawDeets as $targType => $interDeets) {
173
                    if ($targType != $principalType) {
174
                        continue;
175
                    }
176
                    foreach ($interDeets as $dependentProperty => $finalDeets) {
177
                        if ($keyName !== $finalDeets['local']) {
178
                            continue;
179
                        }
180
                        $dependentMult = $finalDeets['multiplicity'];
181
                        list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
182
                            $principalType,
183
                            $principalMult,
184
                            $principalProperty,
185
                            $dependentType,
186
                            $dependentMult,
187
                            $dependentProperty
188
                        );
189
                        if (!in_array($forward, $result)) {
190
                            // add forward relation
191
                            $result[] = $forward;
192
                        }
193
                        if (!in_array($reverse, $result)) {
194
                            // add reverse relation
195
                            $result[] = $reverse;
196
                        }
197
                    }
198
                }
199
            }
200
        }
201
        return $result;
202
    }
203
204
    /**
205
     * @param $principalType
206
     * @param $principalMult
207
     * @param $principalProperty
208
     * @param $dependentType
209
     * @param $dependentMult
210
     * @param $dependentProperty
211
     * @return array[]
212
     */
213
    private function calculateRoundTripRelationsGenForwardReverse(
214
        $principalType,
215
        $principalMult,
216
        $principalProperty,
217
        $dependentType,
218
        $dependentMult,
219
        $dependentProperty
220
    ) {
221
        assert(
222
            in_array($dependentMult, $this->multConstraints[$principalMult]),
223
            'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
224
        );
225
        assert(
226
            in_array($principalMult, $this->multConstraints[$dependentMult]),
227
            'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
228
        );
229
        $forward = [
230
            'principalType' => $principalType,
231
            'principalMult' => $dependentMult,
232
            'principalProp' => $principalProperty,
233
            'dependentType' => $dependentType,
234
            'dependentMult' => $principalMult,
235
            'dependentProp' => $dependentProperty,
236
            'principalRSet' => $principalType,
237
            'dependentRSet' => $dependentType
238
        ];
239
        $reverse = [
240
            'principalType' => $dependentType,
241
            'principalMult' => $principalMult,
242
            'principalProp' => $dependentProperty,
243
            'dependentType' => $principalType,
244
            'dependentMult' => $dependentMult,
245
            'dependentProp' => $principalProperty,
246
            'principalRSet' => $dependentType,
247
            'dependentRSet' => $principalType
248
        ];
249
        return [$forward, $reverse];
250
    }
251
252
    /**
253
     * @param $className
254
     */
255
    protected function checkClassExists($className)
256
    {
257
        if (!array_key_exists($className, $this->relations)) {
258
            $msg = $className . ' does not exist in holder';
259
            throw new \InvalidArgumentException($msg);
260
        }
261
    }
262
}
263