Passed
Push — master ( 2912a5...f80146 )
by Maxim
02:56
created

MethodRunner::executeAccessor()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 1
nop 2
dl 0
loc 10
ccs 0
cts 8
cp 0
crap 6
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 (!($method->isStatic() && $method->isPublic() && !$method->isAbstract())) {
87
                    continue;
88
                } elseif ($method->name === "m_in_$this->method" || $method->name === "m_out_$this->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
     * Executes Axessors method.
98
     *
99
     * @param string $type type name
100
     * @param string $name method name
101
     * @param mixed $value value to read/write
102
     * @param array $args arguments
103
     * @return mixed function result
104
     */
105
    private function executeAxessorsMethod(string $type, string $name, $value, array $args)
106
    {
107
        if ($name == "m_in_$this->method") {
108
            // add support for static properties
109
            $this->propertyData->reflection->setValue($this->object,
110
                call_user_func([$type, "m_in_$this->method"], $value, $args));
111
            $this->propertyData->reflection->setAccessible(false);
112
            return;
113
        } elseif ($name == "m_out_$this->method") {
114
            $result = call_user_func([$type, "m_out_$this->method"], $value, $args);
115
            $this->propertyData->reflection->setAccessible(false);
116
            return $result;
117
        }
118
    }
119
120
    /**
121
     * Executes complex accessor.
122
     *
123
     * @param int $mode running mode
124
     * @param mixed $value field or argument value
125
     * @return mixed new value
126
     * @throws AxessorsError if conditions for accessor did not pass
127
     */
128
    private function executeAccessor(int $mode, $value)
129
    {
130
        $this->checkType($this->propertyData->getTypeTree(), $value);
131
        $conditionsSuit = new ConditionsSuit($mode, $this->propertyData, $this->class, $this->method, $this->object);
132
        if ($conditionsSuit->processConditions($value)) {
133
            $handlersSuit = new HandlersSuit($mode, $this->propertyData, $this->class, $this->object);
134
            $value = $handlersSuit->executeHandlers($value);
135
            return $value;
136
        } else {
137
            throw new AxessorsError("conditions for {$this->class}::{$this->method}() did not pass");
138
        }
139
    }
140
141
    private function is($var, string $type): bool
142
    {
143
        $isExactClass = $var instanceof $type;
144
        $isAxessorsType = ((new \ReflectionClass($type))->hasMethod('is') && call_user_func([$type, 'is'], $var));
145
        return $isExactClass || $isAxessorsType;
146
    }
147
148
    /**
149
     * Checks if the type of new property's value is correct.
150
     *
151
     * @param array $typeTree all possible types
152
     * @param $var mixed new value of the property
153
     * @throws TypeError if the type of new property's value does not match the type defined in Axessors comment
154
     */
155
    private function checkType(array $typeTree, $var): void
156
    {
157
        foreach ($typeTree as $type => $subType) {
158
            if (is_int($type) && $this->is($var, $subType)) {
159
                return;
160
            } elseif (is_iterable($var) && $this->is($var, $type)) {
1 ignored issue
show
Bug introduced by
The function is_iterable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

160
            } elseif (/** @scrutinizer ignore-call */ is_iterable($var) && $this->is($var, $type)) {
Loading history...
161
                foreach ($var as $subVar) {
162
                    $this->checkType($subType, $subVar);
163
                }
164
                return;
165
            }
166
        }
167
        throw new TypeError("not a valid type of {$this->class}::\${$this->propertyData->getName()}");
168
    }
169
}