Completed
Push — new-features ( 3d0810...9c9516 )
by Bogdan
02:59
created

ExtractorDefinitionBuilder::getDepth()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 1
crap 3
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
    protected $type_regexp2 = '/
65
    ^
66
        (?:((\w+\b(?:\(.*\))?(?:\s*\[\s*\])?)(?:\s*\|\s*(?-1))*))
67
        |
68
        (?:\(\s*(?-2)\s*\)(?:\s*\[\s*\])?)
69
        |
70
        (\[\s*\])
71
    $
72
    /xi';
73
74
    /**
75
     * {@inheritdoc}
76
     */
77 19
    public function build(string $definition): ExtractorDefinitionInterface
78
    {
79 19
        $definition = trim($definition);
80
81 19
        if (!$definition) {
82 1
            throw new ExtractorDefinitionBuilderException('Definition must be non-empty string');
83
        }
84
85
        try {
86 18
            if (preg_match($this->type_regexp, $definition, $matches)) {
87 17
                $extractor = $this->buildExtractor($matches['name'], $matches['param'] ?? '', $matches['alt'] ?? '', $this->getDepth($matches), $this->hasGroups($matches));
88
89 16
                return $extractor;
90
            }
91 2
        } catch (ExtractorDefinitionBuilderException $e) {
92
            // We don't care about what specific issue we hit inside,
93
            // for API user it means that the definition is invalid
94
        }
95
96 3
        throw new ExtractorDefinitionBuilderException("Unable to parse definition: '{$definition}'");
97
    }
98
99
    /**
100
     * @param string $name
101
     * @param null|string $param
102
     * @param null|string $alt_definitions
103
     * @param int $depth
104
     * @param bool $groups
105
     *
106
     * @return ExtractorDefinitionInterface
107
     * @throws ExtractorDefinitionBuilderException
108
     */
109 17
    protected function buildExtractor(string $name, string $param, string $alt_definitions, int $depth, bool $groups): ExtractorDefinitionInterface
110
    {
111 17
        $next = null;
112
113 17
        if ('' !== $param && preg_match($this->type_regexp, $param, $matches)) {
114 9
            $next = $this->buildExtractor($matches['name'], $matches['param'] ?? '', $matches['alt'] ?? '', $this->getDepth($matches), $this->hasGroups($matches));
115
        }
116
117 17
        if ($name) {
118 13
            $definition = new PlainExtractorDefinition($name, $next);
119
        } else {
120 10
            $definition = $next;
121
        }
122
123 17
        if ($depth > 0) {
124 6
            if (!$definition && $groups) {
125 1
                throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
126
            }
127
128 5
            $definition = $this->buildArrayDefinition($definition, $depth);
129
        }
130
131 16
        if (!$definition) {
132 1
            throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
133
        }
134
135 15
        if ('' !== $alt_definitions) {
136 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...
137
        }
138
139 15
        return $definition;
140
    }
141
142
    /**
143
     * @param PlainExtractorDefinitionInterface $definition
144
     * @param string $alt_definitions
145
     *
146
     * @return VariableExtractorDefinition
147
     * @throws ExtractorDefinitionBuilderException
148
     */
149 6
    protected function buildVariableDefinition(PlainExtractorDefinitionInterface $definition, string $alt_definitions): VariableExtractorDefinition
150
    {
151 6
        $alt = [$definition];
152
153 6
        while ('' !== $alt_definitions && preg_match($this->type_regexp, $alt_definitions, $matches)) {
154
            // build alt
155 6
            $alt[] = $this->buildExtractor($matches['name'], $matches['param'] ?? '', '', $this->getDepth($matches), $this->hasGroups($matches));
156
157 6
            $alt_definitions = trim($matches['alt'] ?? '');
158
        }
159
160 6
        if ('' !== $alt_definitions) {
161
            // UNEXPECTED
162
            // this should not be possible, but just in case we will ever get here
163
            throw new ExtractorDefinitionBuilderException('Invalid varying definition');
164
        }
165
166 6
        return new VariableExtractorDefinition(...$alt);
167
    }
168
169
    /**
170
     * @param null|ExtractorDefinitionInterface $definition
171
     * @param int $depth
172
     *
173
     * @return ExtractorDefinitionInterface
174
     */
175 5
    protected function buildArrayDefinition(?ExtractorDefinitionInterface $definition, int $depth): ExtractorDefinitionInterface
176
    {
177 5
        while ($depth) {
178 5
            $depth--;
179
            // arrayed definition
180 5
            $definition = new PlainExtractorDefinition('[]', $definition);
181
        }
182
183 5
        return $definition;
184
    }
185
186
    /**
187
     * @param array $matches
188
     *
189
     * @return int
190
     */
191 17
    private function getDepth(array $matches): int
192
    {
193 17
        if (!isset($matches['arr']) || '' === $matches['arr']) {
194 13
            return 0;
195
        }
196
197 6
        return substr_count($matches['arr'], '[');
198
    }
199
200 17
    private function hasGroups(array $matches): bool
201
    {
202 17
        return isset($matches['group']) && '' !== $matches['group'];
203
    }
204
}
205