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