Completed
Pull Request — master (#219)
by Christopher
17:20 queued 05:52
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
    /**
84
     * @param bool $biDir
85
     *
86
     * @throws InvalidOperationException
87
     * @throws \ReflectionException
88
     * @return array
89
     */
90
    protected function getRelationshipsFromMethods(bool $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  array                     $rels
172
     * @param  array                     $hooks
173
     * @throws InvalidOperationException
174
     */
175
    protected function getRelationshipsHasMany(array $rels, array &$hooks)
176
    {
177
        /**
178
         * @var string   $property
179
         * @var Relation $relation
180
         */
181
        foreach ($rels['HasMany'] as $property => $relation) {
182
            if ($relation instanceof MorphMany || $relation instanceof MorphToMany) {
183
                continue;
184
            }
185
            $mult = '*';
186
            $targ = get_class($relation->getRelated());
187
            $keyName = $this->polyglotFkKey($relation);
188
            $localName = $this->polyglotRkKey($relation);
189
            $thruName = $relation instanceof HasManyThrough ?
190
                $this->polyglotThroughKey($relation) :
191
                null;
192
193
            $first = $keyName;
194
            $last = $localName;
195
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
196
        }
197
    }
198
199
    /**
200
     * @param  array                     $rels
201
     * @param  array                     $hooks
202
     * @throws InvalidOperationException
203
     */
204
    protected function getRelationshipsHasOne(array $rels, array &$hooks)
205
    {
206
        /**
207
         * @var string   $property
208
         * @var Relation $foo
209
         */
210
        foreach ($rels['HasOne'] as $property => $foo) {
211
            if ($foo instanceof MorphOne) {
212
                continue;
213
            }
214
            $isBelong = $foo instanceof BelongsTo;
215
            $mult = $isBelong ? '1' : '0..1';
216
            $targ = get_class($foo->getRelated());
217
218
            $keyName = $this->polyglotFkKey($foo);
219
            $localName = $this->polyglotRkKey($foo);
220
            $first = $isBelong ? $localName : $keyName;
221
            $last = $isBelong ? $keyName : $localName;
222
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
223
        }
224
    }
225
226
    /**
227
     * @param  array                     $rels
228
     * @param  array                     $hooks
229
     * @throws InvalidOperationException
230
     */
231
    protected function getRelationshipsKnownPolyMorph(array $rels, array &$hooks)
232
    {
233
        /**
234
         * @var string   $property
235
         * @var Relation $foo
236
         */
237
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
238
            $isMany = $foo instanceof MorphToMany;
239
            $targ = get_class($foo->getRelated());
240
            $mult = $isMany || $foo instanceof MorphMany ? '*' : '1';
241
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
242
243
            $keyName = $this->polyglotFkKey($foo);
244
            $localName = $this->polyglotRkKey($foo);
245
            $first = $isMany ? $keyName : $localName;
246
            $last = $isMany ? $localName : $keyName;
247
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
248
        }
249
    }
250
251
    /**
252
     * @param  array                     $rels
253
     * @param  array                     $hooks
254
     * @throws InvalidOperationException
255
     */
256
    protected function getRelationshipsUnknownPolyMorph(array $rels, array &$hooks)
257
    {
258
        /**
259
         * @var string   $property
260
         * @var Relation $foo
261
         */
262
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
263
            $isMany = $foo instanceof MorphToMany;
264
            $targ = get_class($foo->getRelated());
265
            $mult = $isMany ? '*' : '1';
266
267
            $keyName = $this->polyglotFkKey($foo);
268
            $localName = $this->polyglotRkKey($foo);
269
270
            $first = $keyName;
271
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
272
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
273
        }
274
    }
275
276
    /**
277
     * @param             $hooks
278
     * @param             $foreignField
279
     * @param             $RelationName
280
     * @param             $localKey
281
     * @param             $mult
282
     * @param string|null $relatedObject
283
     * @param mixed|null  $type
284
     * @param mixed|null  $through
285
     */
286
    protected function addRelationsHook(
287
        array &$hooks,
288
        $foreignField,
289
        $RelationName,
290
        $localKey,
291
        $mult,
292
        $relatedObject,
293
        $type = null,
294
        $through = null
295
    ) {
296
        if (!isset($hooks[$foreignField])) {
297
            $hooks[$foreignField] = [];
298
        }
299
        if (!isset($hooks[$foreignField][$relatedObject])) {
300
            $hooks[$foreignField][$relatedObject] = [];
301
        }
302
        $hooks[$foreignField][$relatedObject][$RelationName] = [
303
            'property' => $RelationName,
304
            'local' => $localKey,
305
            'through' => $through,
306
            'multiplicity' => $mult,
307
            'type' => $type
308
        ];
309
    }
310
}
311