Completed
Push — master ( 09273f...7566be )
by Maxim
03:11
created

MethodRunner::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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