1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the pinepain/js-sandbox PHP library. |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2016-2017 Bogdan Padalko <[email protected]> |
7
|
|
|
* |
8
|
|
|
* Licensed under the MIT license: http://opensource.org/licenses/MIT |
9
|
|
|
* |
10
|
|
|
* For the full copyright and license information, please view the |
11
|
|
|
* LICENSE file that was distributed with this source or visit |
12
|
|
|
* http://opensource.org/licenses/MIT |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
|
16
|
|
|
namespace Pinepain\JsSandbox\Specs\Builder; |
17
|
|
|
|
18
|
|
|
|
19
|
|
|
use Pinepain\JsSandbox\Decorators\DecoratorSpecBuilderInterface; |
20
|
|
|
use Pinepain\JsSandbox\Specs\Builder\Exceptions\FunctionSpecBuilderException; |
21
|
|
|
use Pinepain\JsSandbox\Specs\FunctionSpec; |
22
|
|
|
use Pinepain\JsSandbox\Specs\FunctionSpecInterface; |
23
|
|
|
use Pinepain\JsSandbox\Specs\ParametersList; |
24
|
|
|
use Pinepain\JsSandbox\Specs\ParametersListInterface; |
25
|
|
|
use Pinepain\JsSandbox\Specs\ReturnSpec\AnyReturnSpec; |
26
|
|
|
use Pinepain\JsSandbox\Specs\ReturnSpec\ReturnSpecInterface; |
27
|
|
|
use Pinepain\JsSandbox\Specs\ReturnSpec\VoidReturnSpec; |
28
|
|
|
use Pinepain\JsSandbox\Specs\ThrowSpec\EchoThrowSpec; |
29
|
|
|
use Pinepain\JsSandbox\Specs\ThrowSpec\ThrowSpecList; |
30
|
|
|
use Pinepain\JsSandbox\Specs\ThrowSpec\ThrowSpecListInterface; |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
class FunctionSpecBuilder implements FunctionSpecBuilderInterface |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* @var DecoratorSpecBuilderInterface |
37
|
|
|
*/ |
38
|
|
|
private $decorator; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var ParameterSpecBuilderInterface |
42
|
|
|
*/ |
43
|
|
|
private $builder; |
44
|
|
|
/** |
45
|
|
|
* @var array |
46
|
|
|
*/ |
47
|
|
|
private $return_types; |
48
|
|
|
/** |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
private $default_return_type = 'any'; |
52
|
|
|
|
53
|
|
|
private $regexp = '/ |
54
|
|
|
^ |
55
|
|
|
\s* |
56
|
|
|
(?<decorators> |
57
|
|
|
\s* |
58
|
|
|
(?:\@[\w-]+(?:\s*\([^\)]*\)\s*)?\s*)+ |
59
|
|
|
\s* |
60
|
|
|
)? |
61
|
|
|
\( |
62
|
|
|
\s* |
63
|
|
|
(?<params>([^,]+)(?:\s*,\s*[^,]+)*)? |
64
|
|
|
\s* |
65
|
|
|
\) |
66
|
|
|
(?: |
67
|
|
|
\s* |
68
|
|
|
\: |
69
|
|
|
\s* |
70
|
|
|
(?<return>\w+\b)? |
71
|
|
|
\s* |
72
|
|
|
)? |
73
|
|
|
(?: |
74
|
|
|
\s* |
75
|
|
|
throws |
76
|
|
|
\s* |
77
|
|
|
(?<throws>(\\\\?[a-z][\w\\\\]+)(?:\s*\,\s*(?-1))*)? |
78
|
|
|
\s* |
79
|
|
|
)? |
80
|
|
|
\s* |
81
|
|
|
$ |
82
|
|
|
/xi'; |
83
|
|
|
|
84
|
|
|
private $decorators_regexp = '/([^\@"\']+)|("([^"]*)")|(\'([^\']*)\')/i'; |
85
|
|
|
|
86
|
16 |
|
public function __construct(DecoratorSpecBuilderInterface $decorator, ParameterSpecBuilderInterface $builder) |
87
|
|
|
{ |
88
|
16 |
|
$this->decorator = $decorator; |
89
|
16 |
|
$this->builder = $builder; |
90
|
|
|
|
91
|
16 |
|
$this->return_types = [ |
92
|
16 |
|
'any' => new AnyReturnSpec(), |
93
|
16 |
|
'void' => new VoidReturnSpec(), |
94
|
|
|
]; |
95
|
16 |
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* {@inheritdoc} |
99
|
|
|
*/ |
100
|
16 |
|
public function build(string $definition): FunctionSpecInterface |
101
|
|
|
{ |
102
|
16 |
|
$definition = trim($definition); |
103
|
|
|
|
104
|
16 |
|
if (!$definition) { |
105
|
1 |
|
throw new FunctionSpecBuilderException('Definition must be non-empty string'); |
106
|
|
|
} |
107
|
|
|
|
108
|
15 |
|
if (preg_match($this->regexp, $definition, $matches)) { |
109
|
|
|
|
110
|
14 |
|
$decorators = $this->getDecoratorsList($matches['decorators'] ?? ''); |
111
|
14 |
|
$params = $this->getParametersList($matches['params'] ?? ''); |
112
|
14 |
|
$return = $this->getReturnType(($matches['return'] ?? '') ?: $this->default_return_type); |
113
|
13 |
|
$throws = $this->getThrowsList($matches['throws'] ?? ''); |
114
|
|
|
|
115
|
13 |
|
return new FunctionSpec($params, $throws, $return, $decorators); |
116
|
|
|
} |
117
|
|
|
|
118
|
1 |
|
throw new FunctionSpecBuilderException("Unable to parse definition: '{$definition}'"); |
119
|
|
|
} |
120
|
|
|
|
121
|
14 |
|
protected function getReturnType(string $definition): ReturnSpecInterface |
122
|
|
|
{ |
123
|
14 |
|
if (!isset($this->return_types[$definition])) { |
124
|
1 |
|
throw new FunctionSpecBuilderException("Invalid return type: '{$definition}'"); |
125
|
|
|
} |
126
|
|
|
|
127
|
13 |
|
return $this->return_types[$definition]; |
128
|
|
|
} |
129
|
|
|
|
130
|
14 |
|
protected function getParametersList(string $definition): ParametersListInterface |
131
|
|
|
{ |
132
|
14 |
|
$params = []; |
133
|
|
|
|
134
|
14 |
|
if ($definition) { |
135
|
3 |
|
$raw_params_definition = explode(',', $definition); |
136
|
3 |
|
foreach ($raw_params_definition as $param_definition) { |
137
|
3 |
|
$params[] = $this->builder->build(trim($param_definition)); |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
14 |
|
return new ParametersList(...$params); |
142
|
|
|
} |
143
|
|
|
|
144
|
13 |
|
protected function getThrowsList(string $definition): ThrowSpecListInterface |
145
|
|
|
{ |
146
|
13 |
|
$specs = []; |
147
|
|
|
|
148
|
13 |
|
if ($definition) { |
149
|
2 |
|
$classes = array_filter(array_map('\trim', explode(', ', $definition))); |
150
|
|
|
|
151
|
2 |
|
foreach ($classes as $class) { |
152
|
2 |
|
$specs[] = new EchoThrowSpec($class); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
13 |
|
return new ThrowSpecList(...$specs); |
157
|
|
|
} |
158
|
|
|
|
159
|
14 |
|
protected function getDecoratorsList(string $definition): array |
160
|
|
|
{ |
161
|
14 |
|
$definition = trim($definition); |
162
|
|
|
|
163
|
14 |
|
if (!$definition) { |
164
|
10 |
|
return []; |
165
|
|
|
} |
166
|
|
|
|
167
|
4 |
|
$separators = preg_split($this->decorators_regexp, $definition, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); |
168
|
|
|
|
169
|
4 |
|
if (!$separators) { |
|
|
|
|
170
|
|
|
// UNEXPECTED |
171
|
|
|
throw new FunctionSpecBuilderException("Invalid decorators: '{$definition}'"); |
172
|
|
|
} |
173
|
|
|
|
174
|
4 |
|
$decorators = []; |
175
|
|
|
|
176
|
4 |
|
$sequences = array_column($separators, 1); |
177
|
|
|
|
178
|
4 |
|
$sequences[] = strlen($definition); |
179
|
|
|
|
180
|
4 |
|
while (count($sequences) > 1) { |
181
|
4 |
|
$start = array_shift($sequences); |
182
|
4 |
|
$end = $sequences[0]; |
183
|
|
|
|
184
|
4 |
|
$part = trim(substr($definition, $start, $end - $start)); |
185
|
|
|
|
186
|
4 |
|
$decorators[] = $this->decorator->build($part); |
187
|
|
|
} |
188
|
|
|
|
189
|
4 |
|
return $decorators; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.