Completed
Push — master ( 171614...5fca4d )
by Alex
19s queued 11s
created

MetadataRelationsTrait::getCodeForMethod()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 25
rs 9.6666
cc 4
nc 6
nop 1
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