Completed
Pull Request — master (#201)
by Alex
14:36 queued 06:56
created

MetadataRelationsTrait::addRelationsHook()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 14
rs 9.9332
cc 3
nc 4
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: alex
5
 * Date: 13/02/20
6
 * Time: 1:08 PM
7
 */
8
9
namespace AlgoWeb\PODataLaravel\Models;
10
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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
     * @return array
31
     * @throws InvalidOperationException
32
     * @throws \ReflectionException
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
     * @return array
87
     * @throws InvalidOperationException
88
     * @throws \ReflectionException
89
     */
90
    protected function getRelationshipsFromMethods($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 $rels
172
     * @param $hooks
173
     * @throws InvalidOperationException
174
     */
175
    protected function getRelationshipsHasMany($rels, &$hooks)
176
    {
177
        /**
178
         * @var string $property
179
         * @var Relation $foo
180
         */
181
        foreach ($rels['HasMany'] as $property => $foo) {
182
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
183
                continue;
184
            }
185
            $mult = '*';
186
            $targ = get_class($foo->getRelated());
187
            list($thruName, $fkMethodName, $rkMethodName) = $this->getRelationsHasManyKeyNames($foo);
188
189
            $keyRaw = $foo->$fkMethodName();
190
            $keySegments = explode('.', $keyRaw);
191
            $keyName = $keySegments[count($keySegments) - 1];
192
            $localRaw = $foo->$rkMethodName();
193
            $localSegments = explode('.', $localRaw);
194
            $localName = $localSegments[count($localSegments) - 1];
195
            if (null !== $thruName) {
196
                $thruRaw = $foo->$thruName();
197
                $thruSegments = explode('.', $thruRaw);
198
                $thruName = $thruSegments[count($thruSegments) - 1];
199
            }
200
            $first = $keyName;
201
            $last = $localName;
202
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
203
        }
204
    }
205
206
    /**
207
     * @param $rels
208
     * @param $hooks
209
     * @throws InvalidOperationException
210
     */
211
    protected function getRelationshipsHasOne($rels, &$hooks)
212
    {
213
        /**
214
         * @var string $property
215
         * @var Relation $foo
216
         */
217
        foreach ($rels['HasOne'] as $property => $foo) {
218
            if ($foo instanceof MorphOne) {
219
                continue;
220
            }
221
            $isBelong = $foo instanceof BelongsTo;
222
            $mult = $isBelong ? '1' : '0..1';
223
            $targ = get_class($foo->getRelated());
224
225
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
226
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
227
228
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
229
            $keySegments = explode('.', $keyName);
230
            $keyName = $keySegments[count($keySegments) - 1];
231
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
232
            $localSegments = explode('.', $localRaw);
233
            $localName = $localSegments[count($localSegments) - 1];
234
            $first = $isBelong ? $localName : $keyName;
235
            $last = $isBelong ? $keyName : $localName;
236
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
237
        }
238
    }
239
240
    /**
241
     * @param $rels
242
     * @param $hooks
243
     * @throws InvalidOperationException
244
     */
245
    protected function getRelationshipsKnownPolyMorph($rels, &$hooks)
246
    {
247
        /**
248
         * @var string $property
249
         * @var Relation $foo
250
         */
251
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
252
            $isMany = $foo instanceof MorphToMany;
253
            $targ = get_class($foo->getRelated());
254
            $mult = $isMany ? '*' : ($foo instanceof MorphMany ? '*' : '1');
255
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
256
257
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
258
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
259
260
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
261
            $keySegments = explode('.', $keyRaw);
262
            $keyName = $keySegments[count($keySegments) - 1];
263
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
264
            $localSegments = explode('.', $localRaw);
265
            $localName = $localSegments[count($localSegments) - 1];
266
            $first = $isMany ? $keyName : $localName;
267
            $last = $isMany ? $localName : $keyName;
268
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
269
        }
270
    }
271
272
    /**
273
     * @param $rels
274
     * @param $hooks
275
     * @throws InvalidOperationException
276
     */
277
    protected function getRelationshipsUnknownPolyMorph($rels, &$hooks)
278
    {
279
        /**
280
         * @var string $property
281
         * @var Relation $foo
282
         */
283
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
284
            $isMany = $foo instanceof MorphToMany;
285
            $targ = get_class($foo->getRelated());
286
            $mult = $isMany ? '*' : '1';
287
288
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
289
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
290
291
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
292
            $keySegments = explode('.', $keyRaw);
293
            $keyName = $keySegments[count($keySegments) - 1];
294
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
295
            $localSegments = explode('.', $localRaw);
296
            $localName = $localSegments[count($localSegments) - 1];
297
298
            $first = $keyName;
299
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
300
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
301
        }
302
    }
303
304
    /**
305
     * @param             $hooks
306
     * @param             $first
307
     * @param             $property
308
     * @param             $last
309
     * @param             $mult
310
     * @param string|null $targ
311
     * @param mixed|null  $type
312
     * @param mixed|null  $through
313
     */
314
    protected function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ, $type = null, $through = null)
315
    {
316
        if (!isset($hooks[$first])) {
317
            $hooks[$first] = [];
318
        }
319
        if (!isset($hooks[$first][$targ])) {
320
            $hooks[$first][$targ] = [];
321
        }
322
        $hooks[$first][$targ][$property] = [
323
            'property' => $property,
324
            'local' => $last,
325
            'through' => $through,
326
            'multiplicity' => $mult,
327
            'type' => $type
328
        ];
329
    }
330
}
331