Passed
Push — master ( e7d3cc...21de9b )
by Maxim
03:42
created

CallProcessor   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Test Coverage

Coverage 50.98%

Importance

Changes 0
Metric Value
dl 0
loc 158
ccs 26
cts 51
cp 0.5098
rs 10
c 0
b 0
f 0
wmc 25

8 Methods

Rating   Name   Duplication   Size   Complexity  
A in() 0 3 2
A isAccessibleProtected() 0 6 4
A getParentClass() 0 10 3
A isAccessiblePrivate() 0 5 3
A isAccessible() 0 11 4
A __construct() 0 7 1
B searchMethod() 0 19 7
A call() 0 8 1
1
<?php
2
/**
3
 * This file is a part of "Axessors" library.
4
 *
5
 * @author <[email protected]>
6
 * @license GPL
7
 */
8
9
namespace NoOne4rever\Axessors;
10
11
use NoOne4rever\Axessors\Exceptions\{
12
    AxessorsError,
13
    InternalError,
14
    OopError
15
};
16
17
/**
18
 * Class CallProcessor
19
 *
20
 * Handles method call.
21
 *
22
 * @package NoOne4rever\Axessors
23
 */
24
class CallProcessor
25
{
26
    /** @var string method name */
27
    private $method;
28
    /** @var mixed|null an object */
29
    private $object;
30
    /** @var string class name */
31
    private $class;
32
    /** @var int method call line number */
33
    private $line;
34
    /** @var string method call file name */
35
    private $file;
36
    /** @var string the name of calling class */
37
    private $callingClass;
38
    /** @var \ReflectionClass class data */
39
    private $reflection;
40
41
    /**
42
     * CallProcessor constructor.
43
     *
44
     * @param array $backtrace contains call stack
45
     * @param mixed|null $object an object the method is called on (might not be given, if the method is static)
46
     */
47 2
    public function __construct(array $backtrace, $object = null)
48
    {
49 2
        $this->object = $object;
50 2
        $this->class = $backtrace[0]['class'];
51 2
        $this->line = $backtrace[1]['line'];
52 2
        $this->file = $backtrace[1]['file'];
53 2
        $this->callingClass = $backtrace[1]['class'];
54 2
    }
55
56
    /**
57
     * General function of calling the method.
58
     *
59
     * Processes method's name to extract property name and checks if the method is accessible.
60
     *
61
     * @param array $args the arguments of the method called
62
     * @param string $method the name of the method called
63
     * @return mixed return value of the method called
64
     * @throws AxessorsError is thrown when Axessors method not found
65
     * @throws OopError is thrown when the method is not accessible
66
     */
67 2
    public function call(array $args, string $method)
68
    {
69 2
        $classData = Data::getClass($this->class);
70 2
        $this->method = $method;
71 2
        $this->reflection = $classData->reflection;
72 2
        $propertyData = $this->searchMethod($classData);
73 2
        $runner = new MethodRunner(0, $propertyData, $this->class, $this->method, $this->object);
74 2
        return $runner->run($args);
75
    }
76
77
    /**
78
     * Searches called method in Axessors methods.
79
     * 
80
     * @param ClassData $classData class data
81
     * @return PropertyData property that has the called method
82
     * @throws AxessorsError if the called method not found
83
     * @throws OopError if the called method is not accessible
84
     */
85 2
    private function searchMethod(ClassData $classData): PropertyData
86
    {
87 2
        while (!is_null($classData)) {
88 2
            foreach ($classData->getAllProperties() as $propertyData) {
89 2
                foreach ($propertyData->getMethods() as $modifier => $methods) {
90 2
                    foreach ($methods as $method) {
91 2
                        if ($this->method === $method) {
92 2
                            if ($this->isAccessible($modifier, $classData->reflection)) {
93 2
                                return $propertyData;
94
                            } else {
95 2
                                throw new OopError("method $this->class::$this->method() is not accessible there");
96
                            }
97
                        }
98
                    }
99
                }
100
            }
101
            $classData = $this->getParentClass($classData);
102
        }
103
        throw new AxessorsError("method {$this->class}::{$this->method}() not found");
104
    }
105
106
    /**
107
     * Returns parent class data.
108
     * 
109
     * @param ClassData $classData class data
110
     * @return ClassData|null parent class
111
     */
112
    private function getParentClass(ClassData $classData): ?ClassData
113
    {
114
        $reflection = $classData->reflection->getParentClass();
115
        if ($reflection === false) {
116
            return null;
117
        }
118
        try {
119
            return Data::getClass($reflection->name);
120
        } catch (InternalError $error) {
121
            return null;
122
        }
123
    }
124
125
    /**
126
     * Checks if the method is accessible.
127
     *
128
     * @param string $accessModifier access modifier
129
     * @param \ReflectionClass $reflection class data
130
     * @return bool result of the checkout
131
     * @throws InternalError if not a valid it's impossible to check accessibility.
132
     */
133 2
    private function isAccessible(string $accessModifier, \ReflectionClass $reflection): bool
134
    {
135
        switch ($accessModifier) {
136 2
            case 'public':
137 2
                return true;
138
            case 'protected':
139
                return $this->isAccessibleProtected($reflection);
140
            case 'private':
141
                return $this->isAccessiblePrivate($reflection);
142
        }
143
        throw new InternalError('not a valid access modifier given');
144
    }
145
146
    /**
147
     * Checks if a private method is accessible.
148
     * 
149
     * @param \ReflectionClass $reflection reflection
150
     * @return bool the result of the checkout
151
     */
152
    private function isAccessiblePrivate(\ReflectionClass $reflection): bool 
153
    {
154
        $isCalledClass = $this->callingClass === $reflection->name;
155
        $inCalledClass = $this->file === $reflection->getFileName() && $this->in($reflection);
156
        return $isCalledClass && $inCalledClass;
157
    }
158
159
    /**
160
     * Checks if a protected method is accessible.
161
     * 
162
     * @param \ReflectionClass $reflection reflection
163
     * @return bool the result of the checkout
164
     */
165
    private function isAccessibleProtected(\ReflectionClass $reflection): bool 
166
    {
167
        $isSubclass = is_subclass_of($this->callingClass, $reflection->name);
168
        $reflection = new \ReflectionClass($this->callingClass);
169
        $inSubclass = $this->file === $reflection->getFileName() && $this->in($reflection);
170
        return ($isSubclass && $inSubclass) || $this->isAccessiblePrivate($reflection);
171
    }
172
173
    /**
174
     * Checks if the method is called in right place and it is accessible there.
175
     *
176
     * @param \ReflectionClass $reflection class data
177
     * @return bool result of the checkout
178
     */
179
    private function in(\ReflectionClass $reflection): bool
180
    {
181
        return $reflection->getStartLine() <= $this->line && $reflection->getEndLine() >= $this->line;
182
    }
183
}
184