Passed
Push — new-features ( 896bef...e4e533 )
by Bogdan
03:45 queued 41s
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\VariableExtractorDefinition;
23
24
25
class ExtractorDefinitionBuilder implements ExtractorDefinitionBuilderInterface
26
{
27
    /**
28
     * @var string
29
     */
30
    protected $type_regexp = '/
31
    ^
32
    (
33
        (?<name>
34
            [-_\w]*
35
        )
36
        (?:
37
            \s*
38
            (?<group>
39
                \(
40
                \s*
41
                (?<param>
42
                    (?-4)*
43
                    |
44
                    [\w\\\\]+
45
                )
46
                \s*
47
                \)
48
            )
49
        )?
50
        (?:
51
            \s*
52
            (?<arr>(?:\s*\[\s*\]\s*)+)
53
        )?
54
        (?:
55
            \s*
56
            \|
57
            \s*
58
            (?<alt>(?-6))
59
        )?
60
    )
61
    $
62
    /xi';
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 20
    public function build(string $definition): ExtractorDefinitionInterface
68
    {
69 20
        $definition = trim($definition);
70
71 20
        if (!$definition) {
72 1
            throw new ExtractorDefinitionBuilderException('Definition must be non-empty string');
73
        }
74
75
        try {
76 19
            if (preg_match($this->type_regexp, $definition, $matches)) {
77 18
                $extractor = $this->buildExtractor($matches['name'], $matches['param'] ?? '', $matches['alt'] ?? '', $this->getDepth($matches), $this->hasGroups($matches));
78
79 16
                return $extractor;
80
            }
81 3
        } catch (ExtractorDefinitionBuilderException $e) {
82
            // We don't care about what specific issue we hit inside,
83
            // for API user it means that the definition is invalid
84
        }
85
86 4
        throw new ExtractorDefinitionBuilderException("Unable to parse definition: '{$definition}'");
87
    }
88
89
    /**
90
     * @param string $name
91
     * @param null|string $param
92
     * @param null|string $alt_definitions
93
     * @param int $depth
94
     * @param bool $groups
95
     *
96
     * @return null|ExtractorDefinitionInterface
97
     * @throws ExtractorDefinitionBuilderException
98
     */
99 18
    protected function buildExtractor(string $name, string $param, string $alt_definitions, int $depth, bool $groups): ExtractorDefinitionInterface
100
    {
101 18
        $next = null;
102
103 18
        if ('' !== $param && preg_match($this->type_regexp, $param, $matches)) {
104 10
            $next = $this->buildExtractor($matches['name'], $matches['param'] ?? '', $matches['alt'] ?? '', $this->getDepth($matches), $this->hasGroups($matches));
105
        }
106
107 18
        if ($name) {
108 13
            $definition = new PlainExtractorDefinition($name, $next);
109
        } else {
110 11
            $definition = $next;
111
        }
112
113 18
        if ($depth > 0) {
114 6
            $definition = $this->buildArrayDefinition($definition, $depth, $groups);
115
        }
116
117 17
        if (!$definition) {
118 2
            throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
119
        }
120
121 15
        if ('' !== $alt_definitions) {
122 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...
123
        }
124
125 15
        return $definition;
126
    }
127
128
    /**
129
     * @param PlainExtractorDefinitionInterface $definition
130
     * @param string $alt_definitions
131
     *
132
     * @return VariableExtractorDefinition
133
     * @throws ExtractorDefinitionBuilderException
134
     */
135 6
    protected function buildVariableDefinition(PlainExtractorDefinitionInterface $definition, string $alt_definitions): VariableExtractorDefinition
136
    {
137 6
        $alt = [$definition];
138
139 6
        while ('' !== $alt_definitions && preg_match($this->type_regexp, $alt_definitions, $matches)) {
140
            // build alt
141 6
            $alt[] = $this->buildExtractor($matches['name'], $matches['param'] ?? '', '', $this->getDepth($matches), $this->hasGroups($matches));
142
143 6
            $alt_definitions = trim($matches['alt'] ?? '');
144
        }
145
146 6
        if ('' !== $alt_definitions) {
147
            // UNEXPECTED
148
            // this should not be possible, but just in case we will ever get here
149
            throw new ExtractorDefinitionBuilderException('Invalid varying definition');
150
        }
151
152 6
        return new VariableExtractorDefinition(...$alt);
153
    }
154
155
    /**
156
     * @param null|ExtractorDefinitionInterface $definition
157
     * @param int $depth
158
     * @param bool $groups
159
     *
160
     * @return ExtractorDefinitionInterface
161
     * @throws ExtractorDefinitionBuilderException
162
     */
163 6
    protected function buildArrayDefinition(?ExtractorDefinitionInterface $definition, int $depth, bool $groups): ExtractorDefinitionInterface
164
    {
165
        // special case for blank brackets [] which should be the same as any[]
166 6
        if (!$definition) {
167 3
            if ($groups) {
168 1
                throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
169
            }
170
171 2
            $definition = new PlainExtractorDefinition('any');
172
        }
173
174 5
        while ($depth) {
175 5
            $depth--;
176
            // arrayed definition
177 5
            $definition = new PlainExtractorDefinition('[]', $definition);
178
        }
179
180 5
        return $definition;
181
    }
182
183 18
    private function getDepth(array $matches): int
184
    {
185 18
        if (!isset($matches['arr']) || '' === $matches['arr']) {
186 14
            return 0;
187
        }
188
189 7
        return substr_count($matches['arr'], '[');
190
    }
191
192 18
    private function hasGroups(array $matches): bool
193
    {
194 18
        return isset($matches['group']) && '' !== $matches['group'];
195
    }
196
}
197