Completed
Push — master ( 171614...5fca4d )
by Alex
19s queued 11s
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
    /**
115
     * @param bool $biDir
116
     *
117
     * @throws InvalidOperationException
118
     * @throws \ReflectionException
119
     * @return array
120
     */
121
    protected function getRelationshipsFromMethods(bool $biDir = false)
122
    {
123
        $biDirVal = intval($biDir);
124
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
125
        if ($isCached) {
126
            return static::$relationCategories[$biDirVal];
127
        }
128
        /** @var Model $model */
129
        $model = $this;
130
        $relationships = [
131
            'HasOne' => [],
132
            'UnknownPolyMorphSide' => [],
133
            'HasMany' => [],
134
            'KnownPolyMorphSide' => []
135
        ];
136
        $methods = $this->getModelClassMethods($model);
137
        foreach ($methods as $method) {
138
            //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
139
            $reflection = new \ReflectionMethod($model, $method);
140
            $code = $this->getCodeForMethod($reflection);
141
            foreach (static::$relTypes as $relation) {
142
                //Resolve the relation's model to a Relation object.
143
                if (
144
                    !stripos($code, sprintf('$this->%s(', $relation)) ||
145
                    !(($relationObj = $model->$method()) instanceof Relation) ||
146
                    !in_array(MetadataTrait::class, class_uses($relObject = $relationObj->getRelated()))
147
                ) {
148
                    continue;
149
                }
150
                $targetClass = $relation == 'morphTo' ?
151
                    '\Illuminate\Database\Eloquent\Model|\Eloquent' :
152
                    '\\' . get_class($relObject);
153
                $targObject = $biDir ? $relationObj : $targetClass;
154
                $relToKeyMap = [
155
                    'morphedByMany' => ['UnknownPolyMorphSide','HasMany'],
156
                    'morphToMany' => ['KnownPolyMorphSide', 'HasMany'],
157
                    'morphMany' => ['KnownPolyMorphSide', 'HasMany'],
158
                    'hasMany' => ['HasMany'],
159
                    'hasManyThrough' => ['HasMany'],
160
                    'belongsToMany' => ['HasMany'],
161
                    'morphOne' => ['KnownPolyMorphSide', 'HasOne'],
162
                    'hasOne' => ['HasOne'],
163
                    'belongsTo' => ['HasOne'],
164
                    'morphTo' => ['UnknownPolyMorphSide'],
165
                ];
166
                foreach ($relToKeyMap[$relation] as $key) {
167
                    $relationships[$key][$method] = $targObject;
168
                }
169
            }
170
        }
171
        return static::$relationCategories[$biDirVal] = $relationships;
172
    }
173
174
    /**
175
     * @param  array                     $rels
176
     * @param  array                     $hooks
177
     * @throws InvalidOperationException
178
     */
179
    protected function getRelationshipsHasMany(array $rels, array &$hooks)
180
    {
181
        /**
182
         * @var string   $property
183
         * @var Relation $relation
184
         */
185
        foreach ($rels['HasMany'] as $property => $relation) {
186
            if ($relation instanceof MorphMany || $relation instanceof MorphToMany) {
187
                continue;
188
            }
189
            $mult = '*';
190
            $targ = get_class($relation->getRelated());
191
            $keyName = $this->polyglotFkKey($relation);
192
            $localName = $this->polyglotRkKey($relation);
193
            $thruName = $relation instanceof HasManyThrough ?
194
                $this->polyglotThroughKey($relation) :
195
                null;
196
197
            $first = $keyName;
198
            $last = $localName;
199
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
200
        }
201
    }
202
203
    /**
204
     * @param  array                     $rels
205
     * @param  array                     $hooks
206
     * @throws InvalidOperationException
207
     */
208
    protected function getRelationshipsHasOne(array $rels, array &$hooks)
209
    {
210
        /**
211
         * @var string   $property
212
         * @var Relation $foo
213
         */
214
        foreach ($rels['HasOne'] as $property => $foo) {
215
            if ($foo instanceof MorphOne) {
216
                continue;
217
            }
218
            $isBelong = $foo instanceof BelongsTo;
219
            $mult = $isBelong ? '1' : '0..1';
220
            $targ = get_class($foo->getRelated());
221
222
            $keyName = $this->polyglotFkKey($foo);
223
            $localName = $this->polyglotRkKey($foo);
224
            $first = $isBelong ? $localName : $keyName;
225
            $last = $isBelong ? $keyName : $localName;
226
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
227
        }
228
    }
229
230
    /**
231
     * @param  array                     $rels
232
     * @param  array                     $hooks
233
     * @throws InvalidOperationException
234
     */
235
    protected function getRelationshipsKnownPolyMorph(array $rels, array &$hooks)
236
    {
237
        /**
238
         * @var string   $property
239
         * @var Relation $foo
240
         */
241
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
242
            $isMany = $foo instanceof MorphToMany;
243
            $targ = get_class($foo->getRelated());
244
            $mult = $isMany || $foo instanceof MorphMany ? '*' : '1';
245
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
246
247
            $keyName = $this->polyglotFkKey($foo);
248
            $localName = $this->polyglotRkKey($foo);
249
            $first = $isMany ? $keyName : $localName;
250
            $last = $isMany ? $localName : $keyName;
251
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
252
        }
253
    }
254
255
    /**
256
     * @param  array                     $rels
257
     * @param  array                     $hooks
258
     * @throws InvalidOperationException
259
     */
260
    protected function getRelationshipsUnknownPolyMorph(array $rels, array &$hooks)
261
    {
262
        /**
263
         * @var string   $property
264
         * @var Relation $foo
265
         */
266
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
267
            $isMany = $foo instanceof MorphToMany;
268
            $targ = get_class($foo->getRelated());
269
            $mult = $isMany ? '*' : '1';
270
271
            $keyName = $this->polyglotFkKey($foo);
272
            $localName = $this->polyglotRkKey($foo);
273
274
            $first = $keyName;
275
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
276
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
277
        }
278
    }
279
280
    /**
281
     * @param             $hooks
282
     * @param             $foreignField
283
     * @param             $RelationName
284
     * @param             $localKey
285
     * @param             $mult
286
     * @param string|null $relatedObject
287
     * @param mixed|null  $type
288
     * @param mixed|null  $through
289
     */
290
    protected function addRelationsHook(
291
        array &$hooks,
292
        $foreignField,
293
        $RelationName,
294
        $localKey,
295
        $mult,
296
        $relatedObject,
297
        $type = null,
298
        $through = null
299
    ) {
300
        if (!isset($hooks[$foreignField])) {
301
            $hooks[$foreignField] = [];
302
        }
303
        if (!isset($hooks[$foreignField][$relatedObject])) {
304
            $hooks[$foreignField][$relatedObject] = [];
305
        }
306
        $hooks[$foreignField][$relatedObject][$RelationName] = [
307
            'property' => $RelationName,
308
            'local' => $localKey,
309
            'through' => $through,
310
            'multiplicity' => $mult,
311
            'type' => $type
312
        ];
313
    }
314
}
315