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

calculateRoundTripRelationsGenForwardReverse()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 38
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 32
nc 1
nop 6
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) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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