Completed
Pull Request — master (#219)
by Christopher
10:04 queued 01:43
created

MetadataRelationsTrait::addRelationsHook()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 10
c 2
b 0
f 0
dl 0
loc 22
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
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
     * @param \ReflectionMethod $method
84
     * @return string
85
     * @throws InvalidOperationException
86
     */
87
    protected function getCodeForMethod(\ReflectionMethod $method) : string
88
    {
89
        $fileName = $method->getFileName();
90
91
        $file = new \SplFileObject($fileName);
92
        $file->seek($method->getStartLine() - 1);
93
        $code = '';
94
        while ($file->key() < $method->getEndLine()) {
95
            $code .= $file->current();
96
            $file->next();
97
        }
98
99
        $code = trim(preg_replace('/\s\s+/', '', $code));
100
        if (false === stripos($code, 'function')) {
101
            $msg = 'Function definition must have keyword \'function\'';
102
            throw new InvalidOperationException($msg);
103
        }
104
        $begin = strpos($code, 'function(');
105
        $code = substr($code, $begin, strrpos($code, '}') - $begin + 1);
106
        $lastCode = $code[strlen($code) - 1];
107
        if ('}' != $lastCode) {
108
            $msg = 'Final character of function definition must be closing brace';
109
            throw new InvalidOperationException($msg);
110
        }
111
        return $code;
112
    }
113
    /**
114
     * @param bool $biDir
115
     *
116
     * @throws InvalidOperationException
117
     * @throws \ReflectionException
118
     * @return array
119
     */
120
    protected function getRelationshipsFromMethods(bool $biDir = false)
121
    {
122
        $biDirVal = intval($biDir);
123
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
124
        if ($isCached) {
125
            return static::$relationCategories[$biDirVal];
126
        }
127
        /** @var Model $model */
128
        $model = $this;
129
        $relationships = [
130
            'HasOne' => [],
131
            'UnknownPolyMorphSide' => [],
132
            'HasMany' => [],
133
            'KnownPolyMorphSide' => []
134
        ];
135
        $methods = $this->getModelClassMethods($model);
136
        foreach ($methods as $method) {
137
            //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
138
            $reflection = new \ReflectionMethod($model, $method);
139
            $code = $this->getCodeForMethod($reflection);
140
            foreach (static::$relTypes as $relation) {
141
                $search = '$this->' . $relation . '(';
142
                $found = stripos($code, $search);
143
                if (!$found) {
144
                    continue;
145
                }
146
                //Resolve the relation's model to a Relation object.
147
                $relationObj = $model->$method();
148
                if (!($relationObj instanceof Relation)) {
149
                    continue;
150
                }
151
                $relObject = $relationObj->getRelated();
152
                $relatedModel = '\\' . get_class($relObject);
153
                if (!in_array(MetadataTrait::class, class_uses($relatedModel))) {
154
                    continue;
155
                }
156
                $targObject = $biDir ? $relationObj : $relatedModel;
157
                if (in_array($relation, static::$manyRelTypes)) {
158
                    //Collection or array of models (because Collection is Arrayable)
159
                    $relationships['HasMany'][$method] = $targObject;
160
                } elseif ('morphTo' === $relation) {
161
                    // Model isn't specified because relation is polymorphic
162
                    $relationships['UnknownPolyMorphSide'][$method] =
163
                        $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
164
                } else {
165
                    //Single model is returned
166
                    $relationships['HasOne'][$method] = $targObject;
167
                }
168
                if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
169
                    $relationships['KnownPolyMorphSide'][$method] = $targObject;
170
                }
171
                if (in_array($relation, ['morphedByMany'])) {
172
                    $relationships['UnknownPolyMorphSide'][$method] = $targObject;
173
                }
174
            }
175
        }
176
        return static::$relationCategories[$biDirVal] = $relationships;
177
    }
178
179
    /**
180
     * @param  array                     $rels
181
     * @param  array                     $hooks
182
     * @throws InvalidOperationException
183
     */
184
    protected function getRelationshipsHasMany(array $rels, array &$hooks)
185
    {
186
        /**
187
         * @var string   $property
188
         * @var Relation $relation
189
         */
190
        foreach ($rels['HasMany'] as $property => $relation) {
191
            if ($relation instanceof MorphMany || $relation instanceof MorphToMany) {
192
                continue;
193
            }
194
            $mult = '*';
195
            $targ = get_class($relation->getRelated());
196
            $keyName = $this->polyglotFkKey($relation);
197
            $localName = $this->polyglotRkKey($relation);
198
            $thruName = $relation instanceof HasManyThrough ?
199
                $this->polyglotThroughKey($relation) :
200
                null;
201
202
            $first = $keyName;
203
            $last = $localName;
204
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
205
        }
206
    }
207
208
    /**
209
     * @param  array                     $rels
210
     * @param  array                     $hooks
211
     * @throws InvalidOperationException
212
     */
213
    protected function getRelationshipsHasOne(array $rels, array &$hooks)
214
    {
215
        /**
216
         * @var string   $property
217
         * @var Relation $foo
218
         */
219
        foreach ($rels['HasOne'] as $property => $foo) {
220
            if ($foo instanceof MorphOne) {
221
                continue;
222
            }
223
            $isBelong = $foo instanceof BelongsTo;
224
            $mult = $isBelong ? '1' : '0..1';
225
            $targ = get_class($foo->getRelated());
226
227
            $keyName = $this->polyglotFkKey($foo);
228
            $localName = $this->polyglotRkKey($foo);
229
            $first = $isBelong ? $localName : $keyName;
230
            $last = $isBelong ? $keyName : $localName;
231
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
232
        }
233
    }
234
235
    /**
236
     * @param  array                     $rels
237
     * @param  array                     $hooks
238
     * @throws InvalidOperationException
239
     */
240
    protected function getRelationshipsKnownPolyMorph(array $rels, array &$hooks)
241
    {
242
        /**
243
         * @var string   $property
244
         * @var Relation $foo
245
         */
246
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
247
            $isMany = $foo instanceof MorphToMany;
248
            $targ = get_class($foo->getRelated());
249
            $mult = $isMany || $foo instanceof MorphMany ? '*' : '1';
250
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
251
252
            $keyName = $this->polyglotFkKey($foo);
253
            $localName = $this->polyglotRkKey($foo);
254
            $first = $isMany ? $keyName : $localName;
255
            $last = $isMany ? $localName : $keyName;
256
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
257
        }
258
    }
259
260
    /**
261
     * @param  array                     $rels
262
     * @param  array                     $hooks
263
     * @throws InvalidOperationException
264
     */
265
    protected function getRelationshipsUnknownPolyMorph(array $rels, array &$hooks)
266
    {
267
        /**
268
         * @var string   $property
269
         * @var Relation $foo
270
         */
271
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
272
            $isMany = $foo instanceof MorphToMany;
273
            $targ = get_class($foo->getRelated());
274
            $mult = $isMany ? '*' : '1';
275
276
            $keyName = $this->polyglotFkKey($foo);
277
            $localName = $this->polyglotRkKey($foo);
278
279
            $first = $keyName;
280
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
281
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
282
        }
283
    }
284
285
    /**
286
     * @param             $hooks
287
     * @param             $foreignField
288
     * @param             $RelationName
289
     * @param             $localKey
290
     * @param             $mult
291
     * @param string|null $relatedObject
292
     * @param mixed|null  $type
293
     * @param mixed|null  $through
294
     */
295
    protected function addRelationsHook(
296
        array &$hooks,
297
        $foreignField,
298
        $RelationName,
299
        $localKey,
300
        $mult,
301
        $relatedObject,
302
        $type = null,
303
        $through = null
304
    ) {
305
        if (!isset($hooks[$foreignField])) {
306
            $hooks[$foreignField] = [];
307
        }
308
        if (!isset($hooks[$foreignField][$relatedObject])) {
309
            $hooks[$foreignField][$relatedObject] = [];
310
        }
311
        $hooks[$foreignField][$relatedObject][$RelationName] = [
312
            'property' => $RelationName,
313
            'local' => $localKey,
314
            'through' => $through,
315
            'multiplicity' => $mult,
316
            'type' => $type
317
        ];
318
    }
319
}
320