Completed
Pull Request — master (#219)
by Christopher
14:36 queued 08:24
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
                //Resolve the relation's model to a Relation object.
142
                if (
143
                    !stripos($code, sprintf('$this->%s(', $relation)) ||
144
                    !(($relationObj = $model->$method()) instanceof Relation) ||
145
                    !in_array(MetadataTrait::class, class_uses($relObject = $relationObj->getRelated()))
146
                ) {
147
                    continue;
148
                }
149
                $targObject = $biDir ? $relationObj : '\\' . get_class($relObject);
150
                if (in_array($relation, static::$manyRelTypes)) {
151
                    //Collection or array of models (because Collection is Arrayable)
152
                    $relationships['HasMany'][$method] = $targObject;
153
                } elseif ('morphTo' === $relation) {
154
                    // Model isn't specified because relation is polymorphic
155
                    $relationships['UnknownPolyMorphSide'][$method] =
156
                        $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
157
                } else {
158
                    //Single model is returned
159
                    $relationships['HasOne'][$method] = $targObject;
160
                }
161
                if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
162
                    $relationships['KnownPolyMorphSide'][$method] = $targObject;
163
                }
164
                if (in_array($relation, ['morphedByMany'])) {
165
                    $relationships['UnknownPolyMorphSide'][$method] = $targObject;
166
                }
167
            }
168
        }
169
        return static::$relationCategories[$biDirVal] = $relationships;
170
    }
171
172
    /**
173
     * @param  array                     $rels
174
     * @param  array                     $hooks
175
     * @throws InvalidOperationException
176
     */
177
    protected function getRelationshipsHasMany(array $rels, array &$hooks)
178
    {
179
        /**
180
         * @var string   $property
181
         * @var Relation $relation
182
         */
183
        foreach ($rels['HasMany'] as $property => $relation) {
184
            if ($relation instanceof MorphMany || $relation instanceof MorphToMany) {
185
                continue;
186
            }
187
            $mult = '*';
188
            $targ = get_class($relation->getRelated());
189
            $keyName = $this->polyglotFkKey($relation);
190
            $localName = $this->polyglotRkKey($relation);
191
            $thruName = $relation instanceof HasManyThrough ?
192
                $this->polyglotThroughKey($relation) :
193
                null;
194
195
            $first = $keyName;
196
            $last = $localName;
197
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
198
        }
199
    }
200
201
    /**
202
     * @param  array                     $rels
203
     * @param  array                     $hooks
204
     * @throws InvalidOperationException
205
     */
206
    protected function getRelationshipsHasOne(array $rels, array &$hooks)
207
    {
208
        /**
209
         * @var string   $property
210
         * @var Relation $foo
211
         */
212
        foreach ($rels['HasOne'] as $property => $foo) {
213
            if ($foo instanceof MorphOne) {
214
                continue;
215
            }
216
            $isBelong = $foo instanceof BelongsTo;
217
            $mult = $isBelong ? '1' : '0..1';
218
            $targ = get_class($foo->getRelated());
219
220
            $keyName = $this->polyglotFkKey($foo);
221
            $localName = $this->polyglotRkKey($foo);
222
            $first = $isBelong ? $localName : $keyName;
223
            $last = $isBelong ? $keyName : $localName;
224
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
225
        }
226
    }
227
228
    /**
229
     * @param  array                     $rels
230
     * @param  array                     $hooks
231
     * @throws InvalidOperationException
232
     */
233
    protected function getRelationshipsKnownPolyMorph(array $rels, array &$hooks)
234
    {
235
        /**
236
         * @var string   $property
237
         * @var Relation $foo
238
         */
239
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
240
            $isMany = $foo instanceof MorphToMany;
241
            $targ = get_class($foo->getRelated());
242
            $mult = $isMany || $foo instanceof MorphMany ? '*' : '1';
243
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
244
245
            $keyName = $this->polyglotFkKey($foo);
246
            $localName = $this->polyglotRkKey($foo);
247
            $first = $isMany ? $keyName : $localName;
248
            $last = $isMany ? $localName : $keyName;
249
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
250
        }
251
    }
252
253
    /**
254
     * @param  array                     $rels
255
     * @param  array                     $hooks
256
     * @throws InvalidOperationException
257
     */
258
    protected function getRelationshipsUnknownPolyMorph(array $rels, array &$hooks)
259
    {
260
        /**
261
         * @var string   $property
262
         * @var Relation $foo
263
         */
264
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
265
            $isMany = $foo instanceof MorphToMany;
266
            $targ = get_class($foo->getRelated());
267
            $mult = $isMany ? '*' : '1';
268
269
            $keyName = $this->polyglotFkKey($foo);
270
            $localName = $this->polyglotRkKey($foo);
271
272
            $first = $keyName;
273
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
274
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
275
        }
276
    }
277
278
    /**
279
     * @param             $hooks
280
     * @param             $foreignField
281
     * @param             $RelationName
282
     * @param             $localKey
283
     * @param             $mult
284
     * @param string|null $relatedObject
285
     * @param mixed|null  $type
286
     * @param mixed|null  $through
287
     */
288
    protected function addRelationsHook(
289
        array &$hooks,
290
        $foreignField,
291
        $RelationName,
292
        $localKey,
293
        $mult,
294
        $relatedObject,
295
        $type = null,
296
        $through = null
297
    ) {
298
        if (!isset($hooks[$foreignField])) {
299
            $hooks[$foreignField] = [];
300
        }
301
        if (!isset($hooks[$foreignField][$relatedObject])) {
302
            $hooks[$foreignField][$relatedObject] = [];
303
        }
304
        $hooks[$foreignField][$relatedObject][$RelationName] = [
305
            'property' => $RelationName,
306
            'local' => $localKey,
307
            'through' => $through,
308
            'multiplicity' => $mult,
309
            'type' => $type
310
        ];
311
    }
312
}
313