Passed
Push — master ( db8c12...ffb5af )
by Maxim
01:48
created

MethodRunner::executeAccessor()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 1
nop 2
dl 0
loc 10
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
                }
89
                return $this->executeAxessorsMethod($type, $method->name, $value, $args);
90
            }
91
        }
92
        throw new AxessorsError("method {$this->class}::{$this->method}() not found");
93
    }
94
95
    /**
96
     * Executes Axessors method.
97
     * 
98
     * @param string $type type name
99
     * @param string $name method name
100
     * @param mixed $value value to read/write 
101
     * @param array $args arguments
102
     * @return mixed function result
103
     */
104
    private function executeAxessorsMethod(string $type, string $name, $value, array $args)
105
    {
106
        if ($name == "m_in_$this->method") {
107
            // add support for static properties
108
            $this->propertyData->reflection->setValue($this->object,
109
                call_user_func([$type, "m_in_$this->method"], $value, $args));
110
            $this->propertyData->reflection->setAccessible(false);
111
            return;
112
        } elseif ($name == "m_out_$this->method") {
113
            $result = call_user_func([$type, "m_out_$this->method"], $value, $args);
114
            $this->propertyData->reflection->setAccessible(false);
115
            return $result;
116
        }        
117
    }
118
119
    /**
120
     * Executes complex accessor.
121
     *
122
     * @param int $mode running mode
123
     * @param mixed $value field or argument value
124
     * @return mixed new value
125
     * @throws AxessorsError if conditions for accessor did not pass
126
     */
127
    private function executeAccessor(int $mode, $value)
128
    {
129
        $this->checkType($this->propertyData->getTypeTree(), $value);
130
        $conditionsSuit = new ConditionsSuit($mode, $this->propertyData, $this->class, $this->method, $this->object);
131
        if ($conditionsSuit->processConditions($value)) {
132
            $handlersSuit = new HandlersSuit($mode, $this->propertyData, $this->class, $this->object);
133
            $value = $handlersSuit->executeHandlers($value);
134
            return $value;
135
        } else {
136
            throw new AxessorsError("conditions for {$this->class}::{$this->method}() did not pass");
137
        }
138
    }
139
140
    /**
141
     * Checks if the type of new property's value is correct.
142
     *
143
     * @param array $typeTree all possible types
144
     * @param $var mixed new value of the property
145
     * @throws TypeError if the type of new property's value does not match the type defined in Axessors comment
146
     */
147
    private function checkType(array $typeTree, $var): void
148
    {
149
        foreach ($typeTree as $type => $subType) {
150
            if (is_int($type)) {
151
                if ($var instanceof $subType || ((new \ReflectionClass($subType))->hasMethod('is') && call_user_func([
152
                            $subType,
153
                            'is'
154
                        ], $var))) {
155
                    return;
156
                }
157
            } else {
158
                if (is_iterable($var) && ($var instanceof $type || ((new \ReflectionClass($type))->hasMethod('is') && call_user_func([
159
                                $type,
160
                                'is'
161
                            ], $var)))) {
162
                    foreach ($var as $subVar) {
163
                        $this->checkType($subType, $subVar);
164
                    }
165
                    return;
166
                }
167
            }
168
        }
169
        throw new TypeError("not a valid type of {$this->class}::\${$this->propertyData->getName()}");
170
    }
171
}