MethodRunner::isCalledAxessorsMethod()   A
last analyzed

Complexity

Conditions 5
Paths 12

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5

Importance

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