Completed
Push — new-features ( d60963...eaa755 )
by Bogdan
02:55
created

buildMandatoryParameterSpec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
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\Extractors\ExtractorDefinitionBuilderException;
20
use Pinepain\JsSandbox\Extractors\ExtractorDefinitionBuilderInterface;
21
use Pinepain\JsSandbox\Specs\Builder\Exceptions\ArgumentValueBuilderException;
22
use Pinepain\JsSandbox\Specs\Builder\Exceptions\ParameterSpecBuilderException;
23
use Pinepain\JsSandbox\Specs\Parameters\MandatoryParameterSpec;
24
use Pinepain\JsSandbox\Specs\Parameters\OptionalParameterSpec;
25
use Pinepain\JsSandbox\Specs\Parameters\ParameterSpecInterface;
26
use Pinepain\JsSandbox\Specs\Parameters\VariadicParameterSpec;
27
use function strlen;
28
29
30
class ParameterSpecBuilder implements ParameterSpecBuilderInterface
31
{
32
    protected $regexp = '/
33
        ^
34
        (?:
35
            (?<rest>\.{3})
36
            \s*
37
        )?
38
        (?<name>[_a-z]\w*)
39
        \s*
40
        (?<nullable>\?)?
41
        \s*
42
        (?:
43
            \s* = \s*
44
            (?<default>
45
                (?:[+-]?[0-9]+\.?[0-9]*)    # numbers (no exponential notation)
46
                |
47
                (?:\\\'[^\\\']*\\\')        # single-quoted string
48
                |
49
                (?:\"[^\"]*\")              # double-quoted string
50
                |
51
                (?:\[\s*\])                 # empty array
52
                |
53
                (?:\{\s*\})                 # empty object
54
                |
55
                true | false | null
56
            )
57
            \s*
58
        )?
59
        (?:
60
            \s*
61
            \:
62
            \s*
63
            (?<type>(\w*(?:\(.*\))?(?:\[\s*\])?)(?:\s*\|\s*(?-1))*)
64
            \s*
65
        )?
66
        $
67
        /xi';
68
    /**
69
     * @var ExtractorDefinitionBuilderInterface
70
     */
71
    private $extractor;
72
    /**
73
     * @var ArgumentValueBuilderInterface
74
     */
75
    private $argument;
76
77
    /**
78
     * @param ExtractorDefinitionBuilderInterface $extractor
79
     * @param ArgumentValueBuilderInterface $argument
80
     */
81 14
    public function __construct(ExtractorDefinitionBuilderInterface $extractor, ArgumentValueBuilderInterface $argument)
82
    {
83 14
        $this->extractor = $extractor;
84 14
        $this->argument  = $argument;
85 14
    }
86
87
    /**
88
     * @param string $definition
89
     *
90
     * @return ParameterSpecInterface
91
     * @throws ParameterSpecBuilderException
92
     */
93 14
    public function build(string $definition): ParameterSpecInterface
94
    {
95 14
        $definition = trim($definition);
96
97 14
        if (!$definition) {
98 1
            throw new ParameterSpecBuilderException('Definition must be non-empty string');
99
        }
100
101 13
        if (preg_match($this->regexp, $definition, $matches)) {
102
103 12
            $matches = $this->prepareDefinition($matches);
104
105
            try {
106 9
                if ($this->hasRest($matches)) {
107 1
                    return $this->buildVariadicParameterSpec($matches);
108
                }
109
110 8
                if ($this->hasDefault($matches)) {
111 2
                    return $this->buildOptionalParameterSpec($matches, $matches['default']);
112
                }
113
114 6
                if ($this->hasNullable($matches)) {
115 1
                    return $this->buildOptionalParameterSpec($matches, null);
116
                }
117
118 5
                return $this->buildMandatoryParameterSpec($matches);
119 2
            } catch (ExtractorDefinitionBuilderException $e) {
120 1
                throw new ParameterSpecBuilderException("Unable to parse definition because of extractor failure: " . $e->getMessage());
121
            }
122
        }
123
124 1
        throw new ParameterSpecBuilderException("Unable to parse definition: '{$definition}'");
125
    }
126
127 1
    protected function buildVariadicParameterSpec(array $matches): VariadicParameterSpec
128
    {
129 1
        return new VariadicParameterSpec($matches['name'], $this->extractor->build($matches['type']));
130
    }
131
132 3
    protected function buildOptionalParameterSpec(array $matches, ?string $default): OptionalParameterSpec
133
    {
134 3
        if (null !== $default) {
135 2
            $default_definition = $matches['default'];
136
            try {
137 2
                $default = $this->argument->build($default_definition, false);
138 1
            } catch (ArgumentValueBuilderException $e) {
139 1
                throw new ParameterSpecBuilderException("Unknown or unsupported default value format '{$default_definition}'");
140
            }
141
        }
142
143 2
        return new OptionalParameterSpec($matches['name'], $this->extractor->build($matches['type']), $default);
144
    }
145
146 5
    protected function buildMandatoryParameterSpec(array $matches): MandatoryParameterSpec
147
    {
148 5
        return new MandatoryParameterSpec($matches['name'], $this->extractor->build($matches['type']));
149
    }
150
151 12
    protected function prepareDefinition(array $matches): array
152
    {
153 12
        if ($this->hasNullable($matches) && $this->hasRest($matches)) {
154 1
            throw new ParameterSpecBuilderException("Variadic parameter could not be nullable");
155
        }
156
157 11
        if ($this->hasNullable($matches) && $this->hasDefault($matches)) {
158 1
            throw new ParameterSpecBuilderException("Nullable parameter could not have default value");
159
        }
160
161 10
        if ($this->hasRest($matches) && $this->hasDefault($matches)) {
162 1
            throw new ParameterSpecBuilderException('Variadic parameter could have no default value');
163
        }
164
165 9
        if (!$this->hasType($matches)) {
166 3
            $matches['type'] = 'any'; // special case
167
        }
168
169 9
        return $matches;
170
    }
171
172 9
    private function hasType(array $matches): bool
173
    {
174 9
        return isset($matches['type']) && '' !== $matches['type'];
175
    }
176
177 12
    private function hasNullable(array $matches): bool
178
    {
179 12
        return isset($matches['nullable']) && '' !== $matches['nullable'];
180
    }
181
182 12
    private function hasRest(array $matches): bool
183
    {
184 12
        return isset($matches['rest']) && '' !== $matches['rest'];
185
    }
186
187 11
    private function hasDefault(array $matches): bool
188
    {
189 11
        return isset($matches['default']) && '' !== $matches['default'];
190
    }
191
}
192