Passed
Pull Request — master (#201)
by Alex
10:25 queued 04:19
created

MetadataRelationsTrait::addRelationsHook()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 14
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
9
namespace AlgoWeb\PODataLaravel\Models;
10
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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 Mockery\Mock;
18
use POData\Common\InvalidOperationException;
19
20
trait MetadataRelationsTrait
21
{
22
    use MetadataKeyMethodNamesTrait;
23
24
    protected static $relationHooks = [];
25
    protected static $relationCategories = [];
26
27
28
    /**
29
     * Get model's relationships.
30
     *
31
     * @return array
32
     * @throws InvalidOperationException
33
     * @throws \ReflectionException
34
     */
35
    public function getRelationships()
36
    {
37
        if (empty(static::$relationHooks)) {
38
            $hooks = [];
39
40
            $rels = $this->getRelationshipsFromMethods(true);
41
42
            $this->getRelationshipsUnknownPolyMorph($rels, $hooks);
43
44
            $this->getRelationshipsKnownPolyMorph($rels, $hooks);
45
46
            $this->getRelationshipsHasOne($rels, $hooks);
47
48
            $this->getRelationshipsHasMany($rels, $hooks);
49
50
            static::$relationHooks = $hooks;
51
        }
52
53
        return static::$relationHooks;
54
    }
55
56
    /**
57
     * Is this model the known side of at least one polymorphic relation?
58
     *
59
     * @throws InvalidOperationException
60
     * @throws \ReflectionException
61
     */
62
    public function isKnownPolymorphSide()
63
    {
64
        // isKnownPolymorph needs to be checking KnownPolymorphSide results - if you're checking UnknownPolymorphSide,
65
        // you're turned around
66
        $rels = $this->getRelationshipsFromMethods();
67
        return !empty($rels['KnownPolyMorphSide']);
68
    }
69
70
    /**
71
     * Is this model on the unknown side of at least one polymorphic relation?
72
     *
73
     * @throws InvalidOperationException
74
     * @throws \ReflectionException
75
     */
76
    public function isUnknownPolymorphSide()
77
    {
78
        // isUnknownPolymorph needs to be checking UnknownPolymorphSide results - if you're checking KnownPolymorphSide,
79
        // you're turned around
80
        $rels = $this->getRelationshipsFromMethods();
81
        return !empty($rels['UnknownPolyMorphSide']);
82
    }
83
84
    /**
85
     * @param bool $biDir
86
     *
87
     * @return array
88
     * @throws InvalidOperationException
89
     * @throws \ReflectionException
90
     */
91
    protected function getRelationshipsFromMethods($biDir = false)
92
    {
93
        $biDirVal = intval($biDir);
94
        $isCached = isset(static::$relationCategories[$biDirVal]) && !empty(static::$relationCategories[$biDirVal]);
95
        if (!$isCached) {
96
            /** @var Model $model */
97
            $model = $this;
98
            $relationships = [
99
                'HasOne' => [],
100
                'UnknownPolyMorphSide' => [],
101
                'HasMany' => [],
102
                'KnownPolyMorphSide' => []
103
            ];
104
            $methods = $this->getClassMethods($model);
105
            foreach ($methods as $method) {
106
                //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
107
                $reflection = new \ReflectionMethod($model, $method);
108
                $fileName = $reflection->getFileName();
109
110
                $file = new \SplFileObject($fileName);
111
                $file->seek($reflection->getStartLine()-1);
112
                $code = '';
113
                while ($file->key() < $reflection->getEndLine()) {
114
                    $code .= $file->current();
115
                    $file->next();
116
                }
117
118
                $code = trim(preg_replace('/\s\s+/', '', $code));
119
                if (false === stripos($code, 'function')) {
120
                    $msg = 'Function definition must have keyword \'function\'';
121
                    throw new InvalidOperationException($msg);
122
                }
123
                $begin = strpos($code, 'function(');
124
                $code = substr($code, /** @scrutinizer ignore-type */$begin, strrpos($code, '}')-$begin+1);
125
                $lastCode = $code[strlen(/** @scrutinizer ignore-type */$code)-1];
126
                if ('}' != $lastCode) {
127
                    $msg = 'Final character of function definition must be closing brace';
128
                    throw new InvalidOperationException($msg);
129
                }
130
                foreach (static::$relTypes as $relation) {
131
                    $search = '$this->' . $relation . '(';
132
                    $found = stripos(/** @scrutinizer ignore-type */$code, $search);
133
                    if (!$found) {
134
                        continue;
135
                    }
136
                    //Resolve the relation's model to a Relation object.
137
                    $relationObj = $model->$method();
138
                    if (!($relationObj instanceof Relation)) {
139
                        continue;
140
                    }
141
                    $relObject = $relationObj->getRelated();
142
                    $relatedModel = '\\' . get_class($relObject);
143
                    if (!in_array(MetadataTrait::class, class_uses($relatedModel))) {
144
                        continue;
145
                    }
146
                    $targObject = $biDir ? $relationObj : $relatedModel;
147
                    if (in_array($relation, static::$manyRelTypes)) {
148
                        //Collection or array of models (because Collection is Arrayable)
149
                        $relationships['HasMany'][$method] = $targObject;
150
                    } elseif ('morphTo' === $relation) {
151
                        // Model isn't specified because relation is polymorphic
152
                        $relationships['UnknownPolyMorphSide'][$method] =
153
                            $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
154
                    } else {
155
                        //Single model is returned
156
                        $relationships['HasOne'][$method] = $targObject;
157
                    }
158
                    if (in_array($relation, ['morphMany', 'morphOne', 'morphToMany'])) {
159
                        $relationships['KnownPolyMorphSide'][$method] = $targObject;
160
                    }
161
                    if (in_array($relation, ['morphedByMany'])) {
162
                        $relationships['UnknownPolyMorphSide'][$method] = $targObject;
163
                    }
164
                }
165
            }
166
            static::$relationCategories[$biDirVal] = $relationships;
167
        }
168
        return static::$relationCategories[$biDirVal];
169
    }
170
171
    /**
172
     * @param $rels
173
     * @param $hooks
174
     * @throws InvalidOperationException
175
     */
176
    protected function getRelationshipsHasMany($rels, &$hooks)
177
    {
178
        /**
179
         * @var string $property
180
         * @var Relation $foo
181
         */
182
        foreach ($rels['HasMany'] as $property => $foo) {
183
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
184
                continue;
185
            }
186
            $mult = '*';
187
            $targ = get_class($foo->getRelated());
188
            list($thruName, $fkMethodName, $rkMethodName) = $this->getRelationsHasManyKeyNames($foo);
189
190
            $keyRaw = $foo->$fkMethodName();
191
            $keySegments = explode('.', $keyRaw);
192
            $keyName = $keySegments[count($keySegments) - 1];
193
            $localRaw = $foo->$rkMethodName();
194
            $localSegments = explode('.', $localRaw);
195
            $localName = $localSegments[count($localSegments) - 1];
196
            if (null !== $thruName) {
197
                $thruRaw = $foo->$thruName();
198
                $thruSegments = explode('.', $thruRaw);
199
                $thruName = $thruSegments[count($thruSegments) - 1];
200
            }
201
            $first = $keyName;
202
            $last = $localName;
203
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, null, $thruName);
204
        }
205
    }
206
207
    /**
208
     * @param $rels
209
     * @param $hooks
210
     * @throws InvalidOperationException
211
     */
212
    protected function getRelationshipsHasOne($rels, &$hooks)
213
    {
214
        /**
215
         * @var string $property
216
         * @var Relation $foo
217
         */
218
        foreach ($rels['HasOne'] as $property => $foo) {
219
            if ($foo instanceof MorphOne) {
220
                continue;
221
            }
222
            $isBelong = $foo instanceof BelongsTo;
223
            $mult = $isBelong ? '1' : '0..1';
224
            $targ = get_class($foo->getRelated());
225
226
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isBelong);
227
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isBelong);
228
229
            $keyName = $isBelong ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
230
            $keySegments = explode('.', $keyName);
231
            $keyName = $keySegments[count($keySegments) - 1];
232
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
233
            $localSegments = explode('.', $localRaw);
234
            $localName = $localSegments[count($localSegments) - 1];
235
            $first = $isBelong ? $localName : $keyName;
236
            $last = $isBelong ? $keyName : $localName;
237
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
238
        }
239
    }
240
241
    /**
242
     * @param $rels
243
     * @param $hooks
244
     * @throws InvalidOperationException
245
     */
246
    protected function getRelationshipsKnownPolyMorph($rels, &$hooks)
247
    {
248
        /**
249
         * @var string $property
250
         * @var Relation $foo
251
         */
252
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
253
            $isMany = $foo instanceof MorphToMany;
254
            $targ = get_class($foo->getRelated());
255
            $mult = $isMany ? '*' : ($foo instanceof MorphMany ? '*' : '1');
256
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
257
258
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
259
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
260
261
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
262
            $keySegments = explode('.', $keyRaw);
263
            $keyName = $keySegments[count($keySegments) - 1];
264
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
265
            $localSegments = explode('.', $localRaw);
266
            $localName = $localSegments[count($localSegments) - 1];
267
            $first = $isMany ? $keyName : $localName;
268
            $last = $isMany ? $localName : $keyName;
269
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'unknown');
270
        }
271
    }
272
273
    /**
274
     * @param $rels
275
     * @param $hooks
276
     * @throws InvalidOperationException
277
     */
278
    protected function getRelationshipsUnknownPolyMorph($rels, &$hooks)
279
    {
280
        /**
281
         * @var string $property
282
         * @var Relation $foo
283
         */
284
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
285
            $isMany = $foo instanceof MorphToMany;
286
            $targ = get_class($foo->getRelated());
287
            $mult = $isMany ? '*' : '1';
288
289
            list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo, $isMany);
290
            list($fkMethodAlternate, $rkMethodAlternate) = $this->polyglotKeyMethodBackupNames($foo, !$isMany);
291
292
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->$fkMethodAlternate();
293
            $keySegments = explode('.', $keyRaw);
294
            $keyName = $keySegments[count($keySegments) - 1];
295
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->$rkMethodAlternate();
296
            $localSegments = explode('.', $localRaw);
297
            $localName = $localSegments[count($localSegments) - 1];
298
299
            $first = $keyName;
300
            $last = (isset($localName) && '' != $localName) ? $localName : $foo->getRelated()->getKeyName();
301
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ, 'known');
302
        }
303
    }
304
305
    /**
306
     * @param             $hooks
307
     * @param             $first
308
     * @param             $property
309
     * @param             $last
310
     * @param             $mult
311
     * @param string|null $targ
312
     * @param mixed|null  $type
313
     * @param mixed|null  $through
314
     */
315
    protected function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ, $type = null, $through = null)
316
    {
317
        if (!isset($hooks[$first])) {
318
            $hooks[$first] = [];
319
        }
320
        if (!isset($hooks[$first][$targ])) {
321
            $hooks[$first][$targ] = [];
322
        }
323
        $hooks[$first][$targ][$property] = [
324
            'property' => $property,
325
            'local' => $last,
326
            'through' => $through,
327
            'multiplicity' => $mult,
328
            'type' => $type
329
        ];
330
    }
331
332
    /**
333
     * @param Model $model
334
     * @return array
335
     */
336
    protected function getClassMethods(Model $model)
337
    {
338
        $methods = get_class_methods($model);
339
        $filter = function ($method) {
340
            return (!method_exists('Illuminate\Database\Eloquent\Model', $method)
341
                    && !method_exists(Mock::class, $method)
342
                    && !method_exists(MetadataTrait::class, $method)
343
            );
344
        };
345
        $methods = array_filter($methods, $filter);
346
347
        return $methods;
348
    }
349
}
350