FunctionSpecBuilder::getParametersList()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 3
eloc 7
nc 2
nop 1
crap 12
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 11
    /**
49
     * @var string
50 11
     */
51
    private $default_return_type = 'any';
52 11
53 11
    private $regexp = '/
54 11
        ^
55
        \s*
56 11
        (?<decorators>
57
            \s*
58
            (?:\@[\w-]+(?:\s*\([^\)]*\)\s*)?\s*)+
59
            \s*
60
        )?
61 11
        \(
62
            \s*
63 11
            (?<params>([^,]+)(?:\s*,\s*[^,]+)*)?
64
            \s*
65 11
        \)
66 1
        (?:
67
            \s*
68
            \:
69 10
            \s*
70
            (?<return>\w+\b)?
71 9
            \s*
72
        )?
73 9
        (?:
74 9
            \s*
75 8
            throws
76
            \s*
77 8
            (?<throws>(\\\\?[a-z][\w\\\\]+)(?:\s*\,\s*(?-1))*)?
78
            \s*
79
        )?
80 1
        \s*
81
        $
82
        /xi';
83 9
84
    private $decorators_regexp = '/([^\@"\']+)|("([^"]*)")|(\'([^\']*)\')/i';
85 9
86 1
    public function __construct(DecoratorSpecBuilderInterface $decorator, ParameterSpecBuilderInterface $builder)
87
    {
88
        $this->decorator = $decorator;
89 8
        $this->builder   = $builder;
90
91
        $this->return_types = [
92 9
            'any'  => new AnyReturnSpec(),
93
            'void' => new VoidReturnSpec(),
94 9
        ];
95
    }
96 9
97 1
    /**
98 1
     * {@inheritdoc}
99 1
     */
100
    public function build(string $definition): FunctionSpecInterface
101
    {
102
        $definition = trim($definition);
103 9
104
        if (!$definition) {
105
            throw new FunctionSpecBuilderException('Definition must be non-empty string');
106 8
        }
107
108 8
        if (preg_match($this->regexp, $definition, $matches)) {
109
110 8
            $decorators = $this->getDecoratorsList($matches['decorators'] ?? '');
111 1
            $params     = $this->getParametersList($matches['params'] ?? '');
112
            $return     = $this->getReturnType(($matches['return'] ?? '') ?: $this->default_return_type);
113 1
            $throws     = $this->getThrowsList($matches['throws'] ?? '');
114 1
115
            return new FunctionSpec($params, $throws, $return, $decorators);
116
        }
117
118 8
        throw new FunctionSpecBuilderException("Unable to parse definition: '{$definition}'");
119
    }
120
121
    protected function getReturnType(string $definition): ReturnSpecInterface
122
    {
123
        if (!isset($this->return_types[$definition])) {
124
            throw new FunctionSpecBuilderException("Invalid return type: '{$definition}'");
125
        }
126
127
        return $this->return_types[$definition];
128
    }
129
130
    protected function getParametersList(string $definition): ParametersListInterface
131
    {
132
        $params = [];
133
134
        if ($definition) {
135
            $raw_params_definition = explode(',', $definition);
136
            foreach ($raw_params_definition as $param_definition) {
137
                $params[] = $this->builder->build(trim($param_definition));
138
            }
139
        }
140
141
        return new ParametersList(...$params);
142
    }
143
144
    protected function getThrowsList(string $definition): ThrowSpecListInterface
145
    {
146
        $specs = [];
147
148
        if ($definition) {
149
            $classes = array_filter(array_map('\trim', explode(', ', $definition)));
150
151
            foreach ($classes as $class) {
152
                $specs[] = new EchoThrowSpec($class);
153
            }
154
        }
155
156
        return new ThrowSpecList(...$specs);
157
    }
158
159
    protected function getDecoratorsList(string $definition): array
160
    {
161
        $definition = trim($definition);
162
163
        if (!$definition) {
164
            return [];
165
        }
166
167
        $separators = preg_split($this->decorators_regexp, $definition, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
168
169
        if (!$separators) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $separators of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
170
            // UNEXPECTED
171
            throw new FunctionSpecBuilderException("Invalid decorators: '{$definition}'");
172
        }
173
174
        $decorators = [];
175
176
        $sequences = array_column($separators, 1);
177
178
        $sequences[] = strlen($definition);
179
180
        while (count($sequences) > 1) {
181
            $start = array_shift($sequences);
182
            $end   = $sequences[0];
183
184
            $part = trim(substr($definition, $start, $end - $start));
185
186
            $decorators[] = $this->decorator->build($part);
187
        }
188
189
        return $decorators;
190
    }
191
}
192