ModelReflectionHelper::getCodeForMethod()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 11
c 2
b 0
f 0
dl 0
loc 20
rs 9.9
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
6
namespace AlgoWeb\PODataLaravel\Models;
7
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\Eloquent\Relations\Relation;
10
use Mockery\Mock;
11
use POData\Common\InvalidOperationException;
12
use ReflectionException;
13
use ReflectionMethod;
14
use SplFileObject;
15
16
abstract class ModelReflectionHelper
17
{
18
    /** @var string[] */
19
    protected static $relTypes = [
20
        'hasMany',
21
        'hasManyThrough',
22
        'belongsToMany',
23
        'hasOne',
24
        'belongsTo',
25
        'morphOne',
26
        'morphTo',
27
        'morphMany',
28
        'morphToMany',
29
        'morphedByMany'
30
    ];
31
32
    /**
33
     * @param  ReflectionMethod $method
34
     * @return string
35
     */
36
    public static function getCodeForMethod(ReflectionMethod $method): string
37
    {
38
        /** @var string $fileName */
39
        $fileName = $method->getFileName();
40
41
        $file = new SplFileObject($fileName);
42
        $file->seek($method->getStartLine() - 1);
43
        /** @var string $code */
44
        $code = '';
45
        while ($file->key() < $method->getEndLine()) {
46
            $code .= $file->current();
47
            $file->next();
48
        }
49
50
        /** @var string $code */
51
        $code  = preg_replace('/\s\s+/', '', $code);
52
        /** @var int $begin */
53
        $begin = strpos($code, 'function(');
54
        $code  = substr($code, (int)$begin, strrpos($code, '}') - $begin + 1);
55
        return $code;
56
    }
57
58
    /**
59
     * @param  Model $model
60
     * @return string[]
61
     */
62
    public static function getModelClassMethods(Model $model): array
63
    {
64
        $raw = array_values(array_diff(
65
            get_class_methods($model),
66
            get_class_methods(Model::class),
67
            get_class_methods(Mock::class) ?? [],
68
            get_class_methods(MetadataTrait::class)
69
        ));
70
        $whiteList = $model->getVisible();
71
        if (0 < count($whiteList)) {
72
            return array_values(array_intersect($raw, $whiteList));
73
        }
74
        $blackList = $model->getHidden();
75
        return array_values(array_diff($raw, $blackList));
76
    }
77
78
    /**
79
     * @param  Model               $model
80
     * @throws ReflectionException
81
     * @return array|string[]
82
     */
83
    public static function getRelationshipsFromMethods(Model $model): array
84
    {
85
        $relationships = [];
86
        $methods       = self::getModelClassMethods($model);
87
        foreach ($methods as $method) {
88
            //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
89
            $reflection = new ReflectionMethod($model, $method);
90
            $code       = self::getCodeForMethod($reflection);
91
            foreach (static::$relTypes as $relation) {
92
                //Resolve the relation's model to a Relation object.
93
                if (
94
                    !stripos($code, sprintf('$this->%s(', $relation)) ||
95
                    !(($relationObj = $model->{$method}()) instanceof Relation) ||
96
                    !in_array(MetadataTrait::class, class_uses($relationObj->getRelated()))
97
                ) {
98
                    continue;
99
                }
100
                $relationships[]= $method;
101
            }
102
        }
103
        return $relationships;
104
    }
105
}
106