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

MethodRunner::isCalledAxessorsMethod()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 3
nc 12
nop 1
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 30
rs 8.8571
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\{
12
    AxessorsError,
13
    TypeError
14
};
15
16
/**
17
 * Class MethodRunner.
18
 *
19
 * Runs a method generated by Axessors.
20
 *
21
 * @package NoOne4rever\Axessors
22
 */
23
class MethodRunner extends RunningSuit
24
{
25
    /** @var string method name */
26
    private $method;
27
28
    /**
29
     * MethodRunner constructor.
30
     *
31
     * @param int $mode running mode
32
     * @param PropertyData $data property data
33
     * @param string $class class
34
     * @param string $method method name
35
     * @param object|null $object object
36
     */
37 2
    public function __construct(int $mode, PropertyData $data, string $class, string $method, $object = null)
38
    {
39 2
        parent::__construct($mode, $data, $class, $object);
40 2
        $this->method = $method;
41 2
    }
42
43
    /**
44
     * Emulates execution of the method.
45
     *
46
     * @param array $args the arguments of the method called
47
     * @return mixed return value of the method called
48
     * @throws AxessorsError if conditions for executing an accessor did not pass
49
     * @throws AxessorsError if Axessors method not found
50
     */
51 2
    public function run(array $args)
52
    {
53 2
        $prefix = substr($this->method, 0, 3);
54 2
        if ($prefix == 'get') {
55 1
            $this->propertyData->reflection->setAccessible(true);
56 1
            $value = is_null($this->object) ? $this->propertyData->reflection->getValue() : $this->propertyData->reflection->getValue($this->object);
57 1
            $this->propertyData->reflection->setAccessible(false);
58 1
            return $this->executeAccessor(RunningSuit::OUTPUT_MODE, $value);
59 1
        } elseif ($prefix == 'set') {
60 1
            $value = $this->executeAccessor(RunningSuit::INPUT_MODE, $args[0]);
61 1
            $this->propertyData->reflection->setAccessible(true);
62 1
            is_null($this->object) ? $this->propertyData->reflection->setValue($value) : $this->propertyData->reflection->setValue($this->object,
63 1
                $value);
64 1
            $this->propertyData->reflection->setAccessible(false);
65 1
            return;
66
        } else {
67
            $this->method = str_replace(ucfirst($this->propertyData->getName()), 'PROPERTY', $this->method);
68
            return $this->runAxessorsMethod($args);
69
        }
70
    }
71
72
    /**
73
     * Searches and runs Axessors method.
74
     *
75
     * @param array $args method parameters
76
     * @return mixed result of function
77
     * @throws AxessorsError if requested method not found
78
     */
79
    private function runAxessorsMethod(array $args)
80
    {
81
        $this->propertyData->reflection->setAccessible(true);
82
        $value = $this->propertyData->reflection->getValue($this->object);
83
        $this->checkType($this->propertyData->getTypeTree(), $value);
84
        foreach ($this->propertyData->getTypeTree() as $type => $subType) {
85
            $type = is_int($type) ? $subType : $type;
86
            $reflection = new \ReflectionClass($type);
87
            foreach ($reflection->getMethods() as $method) {
88
                if ($this->isCalledAxessorsMethod($method)) {
89
                    return $this->executeAxessorsMethod($type, $method->name, $value, $args);
90
                }
91
            }
92
        }
93
        throw new AxessorsError("method {$this->class}::{$this->method}() not found");
94
    }
95
96
    /**
97
     * Checks if the method given is called method.
98
     * 
99
     * @param \ReflectionMethod $method method
100
     * @return bool the result of the checkout
101
     */
102
    private function isCalledAxessorsMethod(\ReflectionMethod $method): bool 
103
    {
104
        $isAccessible = $method->isStatic() && $method->isPublic() && !$method->isAbstract();
105
        $isCalled = $method->name === "m_in_$this->method" || $method->name === "m_out_$this->method";
106
        return $isAccessible && $isCalled;
107
    }
108
    
109
    /**
110
     * Executes Axessors method.
111
     *
112
     * @param string $type type name
113
     * @param string $name method name
114
     * @param mixed $value value to read/write
115
     * @param array $args arguments
116
     * @return mixed function result
117
     */
118
    private function executeAxessorsMethod(string $type, string $name, $value, array $args)
119
    {
120
        if ($name == "m_in_$this->method") {
121
            // add support for static properties
122
            $this->propertyData->reflection->setValue($this->object,
123
                call_user_func([$type, "m_in_$this->method"], $value, $args));
124
            $this->propertyData->reflection->setAccessible(false);
125
            return;
126
        } elseif ($name == "m_out_$this->method") {
127
            $result = call_user_func([$type, "m_out_$this->method"], $value, $args);
128
            $this->propertyData->reflection->setAccessible(false);
129
            return $result;
130
        }
131
    }
132
133
    /**
134
     * Executes complex accessor.
135
     *
136
     * @param int $mode running mode
137
     * @param mixed $value field or argument value
138
     * @return mixed new value
139
     * @throws AxessorsError if conditions for accessor did not pass
140
     */
141 2
    private function executeAccessor(int $mode, $value)
142
    {
143 2
        $this->checkType($this->propertyData->getTypeTree(), $value);
144 2
        $conditionsSuit = new ConditionsRunner($mode, $this->propertyData, $this->class, $this->method, $this->object);
145 2
        if ($conditionsSuit->processConditions($value)) {
146 2
            $handlersSuit = new HandlersRunner($mode, $this->propertyData, $this->class, $this->object);
147 2
            $value = $handlersSuit->executeHandlers($value);
148 2
            return $value;
149
        } else {
150
            throw new AxessorsError("conditions for {$this->class}::{$this->method}() did not pass");
151
        }
152
    }
153
154
    /**
155
     * Checks if the variable belongs to defined type.
156
     * 
157
     * @param mixed $var variable
158
     * @param string $type type
159
     * @return bool the result of the checkout
160
     */
161 2
    private function is($var, string $type): bool
162
    {
163 2
        $isExactClass = $var instanceof $type;
164 2
        $isAxessorsType = ((new \ReflectionClass($type))->hasMethod('is') && call_user_func([$type, 'is'], $var));
165 2
        return $isExactClass || $isAxessorsType;
166
    }
167
168
    /**
169
     * Checks if the type of new property's value is correct.
170
     *
171
     * @param array $typeTree all possible types
172
     * @param $var mixed new value of the property
173
     * @throws TypeError if the type of new property's value does not match the type defined in Axessors comment
174
     */
175 2
    private function checkType(array $typeTree, $var): void
176
    {
177 2
        foreach ($typeTree as $type => $subType) {
178 2
            if (is_int($type) && $this->is($var, $subType)) {
179 2
                return;
180
            } elseif (is_iterable($var) && $this->is($var, $type)) {
181
                foreach ($var as $subVar) {
182
                    $this->checkType($subType, $subVar);
183
                }
184
                return;
185
            }
186
        }
187
        throw new TypeError("not a valid type of {$this->class}::\${$this->propertyData->getName()}");
188
    }
189
}