Passed
Push — master ( cfb959...e5db0f )
by Maxim
02:56
created

CallProcessor::isAccessibleProtected()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 6
nop 1
dl 0
loc 6
ccs 0
cts 5
cp 0
crap 20
rs 9.2
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 string class name */
30
    private $class;
31
    private $line;
32
    private $file;
33
    private $callingClass;
34
    /** @var \ReflectionClass class data */
35
    private $reflection;
36
37
    /**
38
     * CallProcessor constructor.
39
     *
40
     * @param array $backtrace contains call stack
41
     * @param mixed|null $object an object the method is called on (might not be given, if the method is static)
42
     */
43
    public function __construct(array $backtrace, $object = null)
44
    {
45
        $this->object = $object;
46
        $this->class = $backtrace[0]['class'];
47
        $this->line = $backtrace[1]['line'];
48
        $this->file = $backtrace[1]['file'];
49
        $this->callingClass = $backtrace[1]['class'];
50
    }
51
52
    /**
53
     * General function of calling the method.
54
     *
55
     * Processes method's name to extract property name and checks if the method is accessible.
56
     *
57
     * @param array $args the arguments of the method called
58
     * @param string $method the name of the method called
59
     * @return mixed return value of the method called
60
     * @throws AxessorsError is thrown when Axessors method not found
61
     * @throws OopError is thrown when the method is not accessible
62
     */
63
    public function call(array $args, string $method)
64
    {
65
        $data = Data::getInstance();
66
        $classData = $data->getClass($this->class);
67
        $this->method = $method;
68
        $this->reflection = $classData->reflection;
69
        $propertyData = $this->searchMethod($classData, $data);
70
        $runner = new MethodRunner(0, $propertyData, $this->class, $this->method, $this->object);
71
        return $runner->run($args);
72
    }
73
74
    private function searchMethod(ClassData $classData, Data $data): PropertyData
75
    {
76
        while (!is_null($classData)) {
77
            foreach ($classData->getAllProperties() as $propertyData) {
78
                foreach ($propertyData->getMethods() as $modifier => $methods) {
79
                    foreach ($methods as $method) {
80
                        if ($this->method === $method) {
81
                            if ($this->isAccessible($modifier, $classData->reflection)) {
82
                                return $propertyData;
83
                            } else {
84
                                throw new OopError("method $this->class::$this->method() is not accessible there");
85
                            }
86
                        }
87
                    }
88
                }
89
            }
90
            $classData = $this->getParentClass($classData, $data);
91
        }
92
        throw new AxessorsError("method {$this->class}::{$this->method}() not found");
93
    }
94
95
    private function getParentClass(ClassData $classData, Data $data): ?ClassData
96
    {
97
        $reflection = $classData->reflection->getParentClass();
98
        if ($reflection === false) {
99
            //throw new InternalError('no parent class found');
100
            return null;
101
        }
102
        try {
103
            return $data->getClass($reflection->name);
104
        } catch (InternalError $error) {
105
            return null;
106
        }
107
    }
108
109
    /**
110
     * Checks if the method is accessible.
111
     *
112
     * @param string $accessModifier access modifier
113
     * @param \ReflectionClass $reflection class data
114
     * @return bool result of the checkout
115
     * @throws InternalError if not a valid it's impossible to check accessibility.
116
     */
117
    private function isAccessible(string $accessModifier, \ReflectionClass $reflection): bool
118
    {
119
        switch ($accessModifier) {
120
            case 'public':
121
                return true;
122
            case 'protected':
123
                return $this->isAccessibleProtected($reflection);
124
            case 'private':
125
                return $this->isAccessiblePrivate($reflection);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
126
        } 
127
    }
128
    
129
    private function isAccessiblePrivate(\ReflectionClass $reflection): bool 
130
    {
131
        $isCalledClass = $this->callingClass === $reflection->name;
132
        $inCalledClass = $this->file === $reflection->getFileName() && $this->in($reflection);
133
        return $isCalledClass && $inCalledClass;
134
    }
135
    
136
    private function isAccessibleProtected(\ReflectionClass $reflection): bool 
137
    {
138
        $isSubclass = is_subclass_of($this->callingClass, $reflection->name);
139
        $reflection = new \ReflectionClass($this->callingClass);
140
        $inSubclass = $this->file === $reflection->getFileName() && $this->in($reflection);
141
        return ($isSubclass && $inSubclass) || $this->isAccessiblePrivate($reflection);
142
    }
143
144
    /**
145
     * Checks if the method called in right place and it is accessible there.
146
     *
147
     * @param \ReflectionClass $reflection class data
148
     * @return bool result of the checkout
149
     */
150
    private function in(\ReflectionClass $reflection): bool
151
    {
152
        return $reflection->getStartLine() <= $this->line && $reflection->getEndLine() >= $this->line;
153
    }
154
}
155