Issues (8)

src/CallProcessor.php (1 issue)

Labels
Severity
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 102
    public function __construct(array $backtrace, $object = null)
48
    {
49 102
        $this->object = $object;
50 102
        $this->class = $backtrace[0]['class'];
51 102
        for ($i = 1; ; ++$i) {
52 102
            if (isset($backtrace[$i]['line'])) {
53 102
                $this->line = $backtrace[$i]['line'];
54 102
                $this->file = $backtrace[$i]['file'];
55 102
                break;
56
            }
57
        }
58 102
        $this->callingClass = $backtrace[1]['class'];
59 102
    }
60
61
    /**
62
     * General function of calling the method.
63
     *
64
     * Processes method's name to extract property name and checks if the method is accessible.
65
     *
66
     * @param array $args the arguments of the method called
67
     * @param string $method the name of the method called
68
     * @return mixed return value of the method called
69
     * @throws AxessorsError is thrown when Axessors method not found
70
     * @throws OopError is thrown when the method is not accessible
71
     */
72 102
    public function call(array $args, string $method)
73
    {
74 102
        $classData = Data::getClass($this->class);
75 102
        $this->method = $method;
76 102
        $this->reflection = $classData->reflection;
77 102
        $propertyData = $this->searchMethod($classData);
78 92
        $runner = new MethodRunner(0, $propertyData, $this->class, $this->method, $this->object);
79 92
        return $runner->run($args, $this->file, $this->line);
80
    }
81
82
    /**
83
     * Searches called method in Axessors methods.
84
     * 
85
     * @param ClassData $classData class data
86
     * @return PropertyData property that has the called method
87
     * @throws AxessorsError if the called method not found
88
     * @throws OopError if the called method is not accessible
89
     */
90 102
    private function searchMethod(ClassData $classData): PropertyData
91
    {
92 102
        while (!is_null($classData)) {
93 102
            foreach ($classData->getAllProperties() as $propertyData) {
94 102
                foreach ($propertyData->getMethods() as $modifier => $methods) {
95 102
                    foreach ($methods as $method) {
0 ignored issues
show
The expression $methods of type string is not traversable.
Loading history...
96 102
                        if ($this->method === $method) {
97 96
                            if ($this->isAccessible($modifier, $classData->reflection)) {
98 92
                                return $propertyData;
99
                            } else {
100 80
                                throw new OopError("method $this->class::$this->method() is not accessible there");
101
                            }
102
                        }
103
                    }
104
                }
105
            }
106 12
            $classData = $this->getParentClass($classData);
107
        }
108 6
        throw new AxessorsError("method {$this->class}::{$this->method}() not found");
109
    }
110
111
    /**
112
     * Returns parent class data.
113
     * 
114
     * @param ClassData $classData class data
115
     * @return ClassData|null parent class
116
     */
117 12
    private function getParentClass(ClassData $classData): ?ClassData
118
    {
119 12
        $reflection = $classData->reflection->getParentClass();
120 12
        if ($reflection === false) {
121 2
            return null;
122
        }
123
        try {
124 10
            return Data::getClass($reflection->name);
125 4
        } catch (InternalError $error) {
126 4
            return null;
127
        }
128
    }
129
130
    /**
131
     * Checks if the method is accessible.
132
     *
133
     * @param string $accessModifier access modifier
134
     * @param \ReflectionClass $reflection class data
135
     * @return bool result of the checkout
136
     * @throws InternalError if not a valid it's impossible to check accessibility.
137
     */
138 96
    private function isAccessible(string $accessModifier, \ReflectionClass $reflection): bool
139
    {
140
        switch ($accessModifier) {
141 96
            case 'public':
142 88
                return true;
143 8
            case 'protected':
144 4
                return $this->isAccessibleProtected($reflection);
145 4
            case 'private':
146 4
                return $this->isAccessiblePrivate($reflection);
147
        }
148
        throw new InternalError('not a valid access modifier given');
149
    }
150
151
    /**
152
     * Checks if a private method is accessible.
153
     * 
154
     * @param \ReflectionClass $reflection reflection
155
     * @return bool the result of the checkout
156
     */
157 6
    private function isAccessiblePrivate(\ReflectionClass $reflection): bool 
158
    {
159 6
        $isCalledClass = $this->callingClass === $reflection->name;
160 6
        $inCalledClass = $this->file === $reflection->getFileName() && $this->in($reflection);
161 6
        return $isCalledClass && $inCalledClass;
162
    }
163
164
    /**
165
     * Checks if a protected method is accessible.
166
     * 
167
     * @param \ReflectionClass $reflection reflection
168
     * @return bool the result of the checkout
169
     */
170 4
    private function isAccessibleProtected(\ReflectionClass $reflection): bool 
171
    {
172 4
        $isSubclass = is_subclass_of($this->callingClass, $reflection->name);
173 4
        $reflection = new \ReflectionClass($this->callingClass);
174 4
        $inSubclass = $this->file === $reflection->getFileName() && $this->in($reflection);
175 4
        return ($isSubclass && $inSubclass) || $this->isAccessiblePrivate($reflection);
176
    }
177
178
    /**
179
     * Checks if the method is called in right place and it is accessible there.
180
     *
181
     * @param \ReflectionClass $reflection class data
182
     * @return bool result of the checkout
183
     */
184 4
    private function in(\ReflectionClass $reflection): bool
185
    {
186 4
        return $reflection->getStartLine() <= $this->line && $reflection->getEndLine() >= $this->line;
187
    }
188
}
189