Completed
Pull Request — master (#219)
by Christopher
08:26 queued 03:44
created

MetadataRelationsTrait::isUnknownPolymorphSide()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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