Passed
Push — master ( c2e0e3...51d343 )
by Maxim
02:05 queued 21s
created

CallProcessor   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 52
dl 0
loc 222
rs 7.9487
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
D call() 0 31 9
B isAccessible() 0 18 13
C checkType() 0 23 11
A in() 0 3 2
C run() 0 67 16

How to fix   Complexity   

Complex Class

Complex classes like CallProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CallProcessor, and based on these observations, apply Extract Interface, too.

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 bool accessor mode */
32
    private $mode;
33
    /** @var string class name */
34
    private $class;
35
    /** @var PropertyData property data */
36
    private $propertyData;
37
    /** @var \ReflectionClass class data */
38
    private $reflection;
39
40
    /**
41
     * CallProcessor constructor.
42
     *
43
     * @param array $backtrace contains call stack
44
     * @param mixed|null $object an object the method is called on (might not be given, if the method is static)
45
     */
46
    public function __construct(array $backtrace, $object = null)
47
    {
48
        $this->object = $object;
49
        $this->class = $backtrace[1]['class'] ?? $backtrace[0]['class'];
50
        $this->backtrace = $backtrace[0];
51
    }
52
53
    /**
54
     * General function of calling the method.
55
     *
56
     * Processes method's name to extract property name and checks if the method is accessible.
57
     *
58
     * @param array $args the arguments of the method called
59
     * @param string $method the name of the method called
60
     * @return mixed return value of the method called
61
     * @throws AxessorsError is thrown when Axessors method not found
62
     * @throws OopError is thrown when the method is not accessible
63
     */
64
    public function call(array $args, string $method)
65
    {
66
        $data = Data::getInstance();
67
        $classData = $data->getClass($this->backtrace['class']);
68
        $this->method = $method;
69
        $this->reflection = $classData->reflection;
70
        while (true) {
71
            foreach ($classData->getAllProperties() as $propertyData) {
72
                foreach ($propertyData->getMethods() as $accessModifier => $methods) {
73
                    foreach ($methods as $method) {
74
                        if ($this->method == $method) {
75
                            if ($this->isAccessible($accessModifier, $classData->reflection)) {
76
                                return $this->run($propertyData, $args);
77
                            } else {
78
                                throw new OopError("method {$this->backtrace['class']}::{$this->method}() is not accessible there");
79
                            }
80
                        }
81
                    }
82
                }
83
            }
84
            $reflection = $classData->reflection->getParentClass();
85
            if ($reflection === false) {
86
                break;
87
            }
88
            try {
89
                $classData = $data->getClass($reflection->name);
90
            } catch (InternalError $error) {
91
                break;
92
            }
93
        }
94
        throw new AxessorsError("method {$this->backtrace['class']}::{$this->method}() not found");
95
    }
96
97
    /**
98
     * Emulates execution of the method.
99
     *
100
     * @param PropertyData $propertyData data of the property
101
     * @param array $args the arguments of the method called
102
     * @return mixed return value of the method called
103
     * @throws AxessorsError if conditions for executing an accessor did not pass
104
     * @throws AxessorsError if Axessors method not found
105
     */
106
    private function run(PropertyData $propertyData, array $args)
107
    {
108
        $prefix = substr($this->method, 0, 3);
109
        $this->propertyData = $propertyData;
110
        if ($prefix == 'get') {
111
            $this->mode = true;
112
            $propertyData->reflection->setAccessible(true);
113
            $value = is_null($this->object) ? $propertyData->reflection->getValue() : $propertyData->reflection->getValue($this->object);
114
            $this->checkType($this->propertyData->getTypeTree(), $value);
115
            $propertyData->reflection->setAccessible(false);
116
            $conditionsSuit = new ConditionsSuit(RunningSuit::OUTPUT_MODE, $this->propertyData, $this->class, $this->method, $this->object);
117
            if ($conditionsSuit->processConditions($value)) {
118
                $handlersSuit = new HandlersSuit(RunningSuit::OUTPUT_MODE, $this->propertyData, $this->class, $this->object);
119
                $value = $handlersSuit->executeHandlers($value);
120
                return $value;
121
            } else {
122
                throw new AxessorsError("conditions for {$this->backtrace['class']}::{$this->method}() did not pass");
123
            }
124
        } elseif ($prefix == 'set') {
125
            $this->mode = false;
126
            $this->checkType($this->propertyData->getTypeTree(), $args[0]);
127
            $conditionsSuit = new ConditionsSuit(RunningSuit::INPUT_MODE, $this->propertyData, $this->class, $this->method, $this->object);
128
            if ($conditionsSuit->processConditions($args[0])) {
129
                $propertyData->reflection->setAccessible(true);
130
                $handlersSuit = new HandlersSuit(RunningSuit::INPUT_MODE, $this->propertyData, $this->class, $this->object);
131
                $value = $handlersSuit->executeHandlers($args[0]);
132
                is_null($this->object) ? $propertyData->reflection->setValue($value) : $propertyData->reflection->setValue($this->object,
133
                    $value);
134
                $propertyData->reflection->setAccessible(false);
135
                return;
136
            } else {
137
                throw new AxessorsError("conditions for {$this->backtrace['class']}::{$this->method}() did not pass");
138
            }
139
        } else {
140
            $this->method = str_replace(ucfirst($this->propertyData->getName()), 'PROPERTY', $this->method);
141
            foreach ($this->propertyData->getTypeTree() as $type => $subType) {
142
                $type = is_int($type) ? $subType : $type;
143
                $reflection = new \ReflectionClass($type);
144
                foreach ($reflection->getMethods() as $method) {
145
                    if (preg_match('{^m_(in|out)_}',
146
                            $method->name) && $method->isStatic() && $method->isPublic() && !$method->isAbstract()) {
147
                        if ($method->name == "m_in_$this->method") {
148
                            $propertyData->reflection->setAccessible(true);
149
                            $value = $propertyData->reflection->getValue($this->object);
150
                            $this->checkType($this->propertyData->getTypeTree(), $value);
151
                            // add support for static properties
152
                            $propertyData->reflection->setValue(
153
                                $this->object, call_user_func(
154
                                    [$type, "m_in_$this->method"], $propertyData->reflection->getValue(
155
                                    $this->object
156
                                ), $args
157
                                )
158
                            );
159
                            $propertyData->reflection->setAccessible(false);
160
                            return;
161
                        } elseif ($method->name == "m_out_$this->method") {
162
                            $propertyData->reflection->setAccessible(true);
163
                            $value = $propertyData->reflection->getValue($this->object);
164
                            $this->checkType($this->propertyData->getTypeTree(), $value);
165
                            $result = call_user_func([$type, "m_out_$this->method"], $value, $args);
166
                            $propertyData->reflection->setAccessible(false);
167
                            return $result;
168
                        }
169
                    }
170
                }
171
            }
172
            throw new AxessorsError("method {$this->backtrace['class']}::{$this->method}() not found");
173
        }
174
    }
175
176
    /**
177
     * Checks if the type of new property's value is correct.
178
     *
179
     * @param array $typeTree all possible types
180
     * @param $var mixed new value of the property
181
     * @throws TypeError if the type of new property's value does not match the type defined in Axessors comment
182
     */
183
    private function checkType(array $typeTree, $var): void
184
    {
185
        foreach ($typeTree as $type => $subType) {
186
            if (is_int($type)) {
187
                if ($var instanceof $subType || ((new \ReflectionClass($subType))->hasMethod('is') && call_user_func([
188
                            $subType,
189
                            'is'
190
                        ], $var))) {
191
                    return;
192
                }
193
            } else {
194
                if (is_iterable($var) && ($var instanceof $type || ((new \ReflectionClass($type))->hasMethod('is') && call_user_func([
195
                                $type,
196
                                'is'
197
                            ], $var)))) {
198
                    foreach ($var as $subVar) {
199
                        $this->checkType($subType, $subVar);
200
                    }
201
                    return;
202
                }
203
            }
204
        }
205
        throw new TypeError("not a valid type of {$this->backtrace['class']}::\${$this->propertyData->getName()}");
206
    }
207
208
    /**
209
     * Checks if the method is accessible.
210
     *
211
     * @param string $accessModifier access modifier
212
     * @param \ReflectionClass $reflection class data
213
     * @return bool result of the checkout
214
     * @throws InternalError if not a valid it's impossible to check accessibility.
215
     */
216
    private function isAccessible(string $accessModifier, \ReflectionClass $reflection): bool
217
    {
218
        if ($accessModifier == 'public') {
219
            return true;
220
        }
221
        $isThis = $reflection->name == $this->backtrace['class'];
222
        $inThisFile = $this->backtrace['file'] == $reflection->getFileName() && $this->in($reflection);
223
        if ($accessModifier == 'private') {
224
            return $isThis && $inThisFile;
225
        }
226
        $isThisBranch = is_subclass_of($this->class, $reflection->name) || is_subclass_of($reflection->name,
227
                $this->class);
228
        $reflection = new \ReflectionClass($this->class);
229
        $inBranchFile = $this->backtrace['file'] == $reflection->getFileName() && $this->in($reflection);
230
        if ($accessModifier == 'protected') {
231
            return ($isThis && $inThisFile) || ($isThis && $inBranchFile) || ($isThisBranch && $inBranchFile);
232
        }
233
        throw new InternalError('not a valid access modifier given');
234
    }
235
236
    /**
237
     * Checks if the method called in right place and it is accessible there.
238
     *
239
     * @param \ReflectionClass $reflection class data
240
     * @return bool result of the checkout
241
     */
242
    private function in(\ReflectionClass $reflection): bool
243
    {
244
        return $reflection->getStartLine() <= $this->backtrace['line'] && $reflection->getEndLine() >= $this->backtrace['line'];
245
    }
246
}
247