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

ExtractorDefinitionBuilder::buildExtractor()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 14
cts 14
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 15
nc 24
nop 5
crap 7
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\Extractors;
17
18
19
use Pinepain\JsSandbox\Extractors\Definition\ExtractorDefinitionInterface;
20
use Pinepain\JsSandbox\Extractors\Definition\PlainExtractorDefinition;
21
use Pinepain\JsSandbox\Extractors\Definition\PlainExtractorDefinitionInterface;
22
use Pinepain\JsSandbox\Extractors\Definition\RecursiveExtractorDefinition;
23
use Pinepain\JsSandbox\Extractors\Definition\VariableExtractorDefinition;
24
25
26
class ExtractorDefinitionBuilder implements ExtractorDefinitionBuilderInterface
27
{
28
    /**
29
     * @var string
30
     */
31
    protected $type_regexp = '/
32
    ^
33
    (
34
        (?<name>
35
            [-_\w]*
36
        )
37
        (?:
38
            \s*
39
            (?<group>
40
                \(
41
                \s*
42
                (?<param>
43
                    (?-4)*
44
                    |
45
                    [\w\\\\]+
46
                )
47
                \s*
48
                \)
49
            )
50
        )?
51
        (?:
52
            \s*
53
            (?<arr>(?:\s*\[\s*\]\s*)+)
54
        )?
55
        (?:
56
            \s*
57
            \|
58
            \s*
59
            (?<alt>(?-6))
60
        )?
61
    )
62
    $
63
    /xi';
64
65
    /**
66
     * {@inheritdoc}
67
     */
68 20
    public function build(string $definition): ExtractorDefinitionInterface
69
    {
70 20
        $definition = trim($definition);
71
72 20
        if (!$definition) {
73 1
            throw new ExtractorDefinitionBuilderException('Definition must be non-empty string');
74
        }
75
76
        try {
77 19
            if (preg_match($this->type_regexp, $definition, $matches)) {
78 18
                $extractor = $this->buildExtractor($matches['name'], $matches['param'] ?? '', $matches['alt'] ?? '', $this->getDepth($matches), $this->hasGroups($matches));
79
80 16
                return $extractor;
81
            }
82 3
        } catch (ExtractorDefinitionBuilderException $e) {
83
            // We don't care about what specific issue we hit inside,
84
            // for API user it means that the definition is invalid
85
        }
86
87 4
        throw new ExtractorDefinitionBuilderException("Unable to parse definition: '{$definition}'");
88
    }
89
90
    /**
91
     * @param string $name
92
     * @param null|string $param
93
     * @param null|string $alt_definitions
94
     * @param int $depth
95
     * @param bool $groups
96
     *
97
     * @return null|ExtractorDefinitionInterface
98
     * @throws ExtractorDefinitionBuilderException
99
     */
100 18
    protected function buildExtractor(string $name, string $param, string $alt_definitions, int $depth, bool $groups): ExtractorDefinitionInterface
101
    {
102 18
        $next = null;
103
104 18
        if ('' !== $param && preg_match($this->type_regexp, $param, $matches)) {
105 10
            $next = $this->buildExtractor($matches['name'], $matches['param'] ?? '', $matches['alt'] ?? '', $this->getDepth($matches), $this->hasGroups($matches));
106
        }
107
108 18
        if ($name) {
109 13
            $definition = $this->buildPlainExtractor($name, $next);
110
        } else {
111 11
            $definition = $next;
112
        }
113
114 18
        if ($depth > 0) {
115 6
            $definition = $this->buildArrayDefinition($definition, $depth, $groups);
116
        }
117
118 17
        if (!$definition) {
119 2
            throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
120
        }
121
122 15
        if ('' !== $alt_definitions) {
123 6
            $definition = $this->buildVariableDefinition($definition, $alt_definitions);
0 ignored issues
show
Compatibility introduced by
$definition of type object<Pinepain\JsSandbo...torDefinitionInterface> is not a sub-type of object<Pinepain\JsSandbo...torDefinitionInterface>. It seems like you assume a child interface of the interface Pinepain\JsSandbox\Extra...ctorDefinitionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
124
        }
125
126 15
        return $definition;
127
    }
128
129
    /**
130
     * @param PlainExtractorDefinitionInterface $definition
131
     * @param string $alt_definitions
132
     *
133
     * @return VariableExtractorDefinition
134
     * @throws ExtractorDefinitionBuilderException
135
     */
136 6
    protected function buildVariableDefinition(PlainExtractorDefinitionInterface $definition, string $alt_definitions): VariableExtractorDefinition
137
    {
138 6
        $alt = [$definition];
139
140 6
        while ('' !== $alt_definitions && preg_match($this->type_regexp, $alt_definitions, $matches)) {
141
            // build alt
142 6
            $alt[] = $this->buildExtractor($matches['name'], $matches['param'] ?? '', '', $this->getDepth($matches), $this->hasGroups($matches));
143
144 6
            $alt_definitions = trim($matches['alt'] ?? '');
145
        }
146
147 6
        if ('' !== $alt_definitions) {
148
            // UNEXPECTED
149
            // this should not be possible, but just in case we will ever get here
150
            throw new ExtractorDefinitionBuilderException('Invalid varying definition');
151
        }
152
153 6
        return new VariableExtractorDefinition(...$alt);
154
    }
155
156
    /**
157
     * @param null|ExtractorDefinitionInterface $definition
158
     * @param int $depth
159
     * @param bool $groups
160
     *
161
     * @return ExtractorDefinitionInterface
162
     * @throws ExtractorDefinitionBuilderException
163
     */
164 6
    protected function buildArrayDefinition(?ExtractorDefinitionInterface $definition, int $depth, bool $groups): ExtractorDefinitionInterface
165
    {
166
        // special case for blank brackets [] which should be the same as any[]
167 6
        if (!$definition) {
168 3
            if ($groups) {
169 1
                throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
170
            }
171
172 2
            $definition = $this->buildPlainExtractor('any');
173
        }
174
175 5
        while ($depth) {
176 5
            $depth--;
177
            // arrayed definition
178 5
            $definition = $this->buildPlainExtractor('[]', $definition);
179
        }
180
181 5
        return $definition;
182
    }
183
184 18
    private function getDepth(array $matches): int
185
    {
186 18
        if (!isset($matches['arr']) || '' === $matches['arr']) {
187 14
            return 0;
188
        }
189
190 7
        return substr_count($matches['arr'], '[');
191
    }
192
193 18
    private function hasGroups(array $matches): bool
194
    {
195 18
        return isset($matches['group']) && '' !== $matches['group'];
196
    }
197
198 15
    private function buildPlainExtractor(string $name, ?ExtractorDefinitionInterface $next = null): PlainExtractorDefinitionInterface
199
    {
200 15
        if ('any' === $name && !$next) {
201 2
            return new RecursiveExtractorDefinition($name);
202
        }
203
204 15
        return new PlainExtractorDefinition($name, $next);
205
    }
206
}
207