Passed
Push — master ( 51d343...db8c12 )
by Maxim
01:52
created

MethodRunner::executeAxessorsMethod()   D

Complexity

Conditions 9
Paths 4

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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