Completed
Push — master ( 344583...2d30d0 )
by Bogdan
11s
created

FunctionSpecBuilder::build()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 11
cts 11
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 3
nop 1
crap 4
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) {
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 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