Completed
Push — master ( bb0ee0...03243e )
by Nico
01:35
created

AST.php$0 ➔ getCallable()   B

Complexity

Conditions 6

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 15
cts 15
cp 1
rs 8.8977
c 0
b 0
f 0
cc 6
crap 6
1
<?php declare(strict_types=1);
2
3
/**
4
 * @license     http://opensource.org/licenses/mit-license.php MIT
5
 * @link        https://github.com/nicoSWD
6
 * @author      Nicolas Oelgart <[email protected]>
7
 */
8
namespace nicoSWD\Rule\TokenStream;
9
10
use Closure;
11
use InvalidArgumentException;
12
use nicoSWD\Rule\Grammar\CallableUserFunctionInterface;
13
use nicoSWD\Rule\Parser\Exception\ParserException;
14
use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException;
15
use nicoSWD\Rule\TokenStream\Token\BaseToken;
16
use nicoSWD\Rule\TokenStream\Token\TokenFactory;
17
use nicoSWD\Rule\Tokenizer\TokenizerInterface;
18
use nicoSWD\Rule\TokenStream\Token\TokenObject;
19
20
class AST
21
{
22
    /** @var TokenizerInterface */
23
    private $tokenizer;
24
    /** @var TokenFactory */
25
    private $tokenFactory;
26
    /** @var TokenStreamFactory */
27
    private $tokenStreamFactory;
28
    /** @var Closure[] */
29
    private $functions = [];
30
    /** @var string[] */
31
    private $methods = [];
32
    /** @var mixed[] */
33
    private $variables = [];
34
35 234
    public function __construct(
36
        TokenizerInterface $tokenizer,
37
        TokenFactory $tokenFactory,
38
        TokenStreamFactory $tokenStreamFactory
39
    ) {
40 234
        $this->tokenizer = $tokenizer;
41 234
        $this->tokenFactory = $tokenFactory;
42 234
        $this->tokenStreamFactory = $tokenStreamFactory;
43 234
    }
44
45 228
    public function getStream(string $rule): TokenStream
46
    {
47 228
        return $this->tokenStreamFactory->create($this->tokenizer->tokenize($rule), $this);
48
    }
49
50 118
    public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface
51
    {
52 118
        if ($token instanceof TokenObject) {
53 10
            return $this->getCallableUserObject($token, $methodName);
54
        }
55
56 108
        if (empty($this->methods)) {
57 108
            $this->registerMethods();
58
        }
59
60 108
        if (!isset($this->methods[$methodName])) {
61 4
            throw new Exception\UndefinedMethodException($methodName);
62
        }
63
64 104
        return new $this->methods[$methodName]($token);
65
    }
66
67 108
    private function registerMethods()
68
    {
69 108
        $this->methods = $this->tokenizer->getGrammar()->getInternalMethods();
70 108
    }
71
72 228
    public function setVariables(array $variables)
73
    {
74 228
        $this->variables = $variables;
75 228
    }
76
77 100
    public function getVariable(string $variableName): BaseToken
78
    {
79 100
        if (!$this->variableExists($variableName)) {
80 4
            throw new UndefinedVariableException($variableName);
81
        }
82
83 96
        return $this->tokenFactory->createFromPHPType($this->variables[$variableName]);
84
    }
85
86 100
    public function variableExists(string $variableName): bool
87
    {
88 100
        return array_key_exists($variableName, $this->variables);
89
    }
90
91 32
    public function getFunction(string $functionName): Closure
92
    {
93 32
        if (empty($this->functions)) {
94 32
            $this->registerFunctions();
95
        }
96
97 32
        if (!isset($this->functions[$functionName])) {
98 6
            throw new Exception\UndefinedFunctionException($functionName);
99
        }
100
101 26
        return $this->functions[$functionName];
102
    }
103
104
    private function registerFunctionClass(string $functionName, string $className)
105
    {
106 26
        $this->functions[$functionName] = function (BaseToken ...$args) use ($className): BaseToken {
107 26
            $function = new $className();
108
109 26
            if (!$function instanceof CallableUserFunctionInterface) {
110 2
                throw new InvalidArgumentException(
111 2
                    sprintf(
112 2
                        "%s must be an instance of %s",
113 2
                        $className,
114 2
                        CallableUserFunctionInterface::class
115
                    )
116
                );
117
            }
118
119 24
            return $function->call(...$args);
0 ignored issues
show
Documentation introduced by
$args is of type array<integer,object<nic...tream\Token\BaseToken>>, but the function expects a null|object<nicoSWD\Rule...Stream\Token\BaseToken>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
120
        };
121 30
    }
122
123 32
    private function registerFunctions()
124
    {
125 32
        foreach ($this->tokenizer->getGrammar()->getInternalFunctions() as $functionName => $className) {
126 30
            $this->registerFunctionClass($functionName, $className);
127
        }
128 32
    }
129
130
    private function getCallableUserObject(BaseToken $token, string $methodName): CallableUserFunctionInterface
131
    {
132
        return new class ($token, $this->tokenFactory, $methodName) implements CallableUserFunctionInterface {
133
            /** @var TokenFactory */
134
            private $tokenFactory;
135
            /** @var Closure */
136
            private $callable;
137
            /** @var array */
138
            private $variations = ['get', 'is', ''];
139
140 10
            public function __construct(BaseToken $token, TokenFactory $tokenFactory, string $methodName)
141
            {
142 10
                $this->tokenFactory = $tokenFactory;
143 10
                $this->callable = $this->getCallable($token, $methodName);
144 8
            }
145
146 10
            private function getCallable(BaseToken $token, string $methodName): Closure
147
            {
148 10
                $object = $token->getValue();
149
150 10
                if (property_exists($object, $methodName)) {
151 2
                    return function () use ($object, $methodName) {
152 2
                        return $object->{$methodName};
153 2
                    };
154
                }
155
156 8
                $method[0] = $object;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$method was never initialized. Although not strictly required by PHP, it is generally a good practice to add $method = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
157 8
                $index = 0;
158
159
                do {
160 8
                    if (!isset($this->variations[$index])) {
161 2
                        throw ParserException::undefinedMethod($methodName, $token);
162
                    }
163
164 8
                    $method[1] = $this->variations[$index] . $methodName;
165 8
                } while (!is_callable($method) && isset($this->variations[$index++]));
166
167 6
                return function (BaseToken $param = null) use ($method) {
168 6
                    return $method($param ? $param->getValue() : null);
169 6
                };
170
            }
171
172 8
            public function call(BaseToken $param = null): BaseToken
173
            {
174 8
                $callable = $this->callable;
175
176 8
                return $this->tokenFactory->createFromPHPType(
177 8
                    $callable($param)
178
                );
179
            }
180
        };
181
    }
182
}
183