Passed
Push — master ( 51d343...db8c12 )
by Maxim
01:52
created

CallProcessor::getParentClass()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 2
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
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\AxessorsError;
12
use NoOne4rever\Axessors\Exceptions\InternalError;
13
use NoOne4rever\Axessors\Exceptions\OopError;
14
use NoOne4rever\Axessors\Exceptions\TypeError;
15
16
/**
17
 * Class CallProcessor
18
 *
19
 * Handles method call.
20
 *
21
 * @package NoOne4rever\Axessors
22
 */
23
class CallProcessor
24
{
25
    /** @var string method name */
26
    private $method;
27
    /** @var mixed|null an object */
28
    private $object;
29
    /** @var mixed call stack */
30
    private $backtrace;
31
    /** @var string class name */
32
    private $class;
33
    /** @var \ReflectionClass class data */
34
    private $reflection;
35
36
    /**
37
     * CallProcessor constructor.
38
     *
39
     * @param array $backtrace contains call stack
40
     * @param mixed|null $object an object the method is called on (might not be given, if the method is static)
41
     */
42
    public function __construct(array $backtrace, $object = null)
43
    {
44
        $this->object = $object;
45
        $this->class = $backtrace[0]['class'];
46
        $this->backtrace = $backtrace[count($backtrace) - 1];
47
    }
48
49
    /**
50
     * General function of calling the method.
51
     *
52
     * Processes method's name to extract property name and checks if the method is accessible.
53
     *
54
     * @param array $args the arguments of the method called
55
     * @param string $method the name of the method called
56
     * @return mixed return value of the method called
57
     * @throws AxessorsError is thrown when Axessors method not found
58
     * @throws OopError is thrown when the method is not accessible
59
     */
60
    public function call(array $args, string $method)
61
    {
62
        $data = Data::getInstance();
63
        $classData = $data->getClass($this->class);
64
        $this->method = $method;
65
        $this->reflection = $classData->reflection;
66
        $propertyData = $this->searchMethod($classData, $data);
67
        $runner = new MethodRunner(0, $propertyData, $this->class, $this->method, $this->object);
68
        return $runner->run($args);
69
    }
70
71
    private function searchMethod(ClassData $classData, Data $data): PropertyData
72
    {
73
        while (!is_null($classData)) {
74
            foreach ($classData->getAllProperties() as $propertyData) {
75
                foreach ($propertyData->getMethods() as $modifier => $methods) {
76
                    foreach ($methods as $method) {
77
                        if ($this->method === $method) {
78
                            if ($this->isAccessible($modifier, $classData->reflection)) {
79
                                return $propertyData;
80
                            } else {
81
                                throw new OopError("method $this->class::$this->method() is not accessible there");
82
                            }
83
                        }
84
                    }
85
                }
86
            }
87
            $classData = $this->getParentClass($classData, $data);
88
        }
89
        throw new AxessorsError("method {$this->class}::{$this->method}() not found");
90
    }
91
92
    private function getParentClass(ClassData $classData, Data $data): ?ClassData
93
    {
94
        $reflection = $classData->reflection->getParentClass();
95
        if ($reflection === false) {
96
            throw new InternalError('no parent class found');
97
        }
98
        try {
99
            return $data->getClass($reflection->name);
100
        } catch (InternalError $error) {
101
            return null;
102
        }
103
    }
104
105
    /**
106
     * Checks if the method is accessible.
107
     *
108
     * @param string $accessModifier access modifier
109
     * @param \ReflectionClass $reflection class data
110
     * @return bool result of the checkout
111
     * @throws InternalError if not a valid it's impossible to check accessibility.
112
     */
113
    private function isAccessible(string $accessModifier, \ReflectionClass $reflection): bool
114
    {
115
        if ($accessModifier == 'public') {
116
            return true;
117
        }
118
        $isThis = $reflection->name == $this->backtrace['class'];
119
        $inThisFile = $this->backtrace['file'] == $reflection->getFileName() && $this->in($reflection);
120
        if ($accessModifier == 'private') {
121
            return $isThis && $inThisFile;
122
        }
123
        $isThisBranch = is_subclass_of($this->backtrace['class'], $reflection->name) || is_subclass_of($reflection->name,
124
                $this->backtrace['class']);
125
        $reflection = new \ReflectionClass($this->class);
126
        $inBranchFile = $this->backtrace['file'] == $reflection->getFileName() && $this->in(new \ReflectionClass($this->backtrace['class']));
127
        if ($accessModifier == 'protected') {
128
            return ($isThis && $inThisFile) || ($isThis && $inBranchFile) || ($isThisBranch && $inBranchFile);
129
        }
130
        throw new InternalError('not a valid access modifier given');
131
    }
132
133
    /**
134
     * Checks if the method called in right place and it is accessible there.
135
     *
136
     * @param \ReflectionClass $reflection class data
137
     * @return bool result of the checkout
138
     */
139
    private function in(\ReflectionClass $reflection): bool
140
    {
141
        return $reflection->getStartLine() <= $this->backtrace['line'] && $reflection->getEndLine() >= $this->backtrace['line'];
142
    }
143
}
144