Passed
Push — master ( b907cf...cfb959 )
by Maxim
02:57
created

MethodRunner::is()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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