Passed
Push — new-features ( 4436b0 )
by Bogdan
05:26 queued 02:19
created

ExtractorDefinitionBuilder::getArrDepth()   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'] ?? null, $matches['alt'] ?? null, $this->getArrDepth($matches), $this->hasGrops($matches));
88
89 15
                if ($extractor) {
90 16
                    return $extractor;
91
                }
92
            }
93 2
        } catch (ExtractorDefinitionBuilderException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
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 $arr_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 $arr_depth, bool $groups): ExtractorDefinitionInterface
110
    {
111 17
        $next = null;
112
113 17
        if ($param && preg_match($this->type_regexp, $param, $matches)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $param of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
114 9
            $next = $this->buildExtractor($matches['name'], $matches['param'] ?? null, $matches['alt'] ?? null, $this->getArrDepth($matches), $this->hasGrops($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 ($arr_depth) {
124 6
            if (!$definition && $groups) {
125 1
                throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
126
            }
127
128 5
            $definition = $this->buildArrayDefinition($definition, $arr_depth);
129
        }
130
131 16
        if ($alt_definitions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $alt_definitions of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
132 6
            $definition = $this->buildVariableDefinition($definition, $alt_definitions);
133
        }
134
135 16
        if (!$definition) {
136 1
            throw new ExtractorDefinitionBuilderException('Empty group is not allowed');
137
        }
138
139
140 15
        return $definition;
141
    }
142
143
    /**
144
     * @param PlainExtractorDefinitionInterface $definition
145
     * @param string $alt_definitions
146
     *
147
     * @return VariableExtractorDefinition
148
     * @throws ExtractorDefinitionBuilderException
149
     */
150 6
    protected function buildVariableDefinition(PlainExtractorDefinitionInterface $definition, string $alt_definitions): VariableExtractorDefinition
151
    {
152 6
        $alt = [$definition];
153
154 6
        while ($alt_definitions && preg_match($this->type_regexp, $alt_definitions, $matches)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $alt_definitions of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
155
            // build alt
156 6
            $alt[] = $this->buildExtractor($matches['name'], $matches['param'] ?? null, null, $this->getArrDepth($matches), $this->hasGrops($matches));
157
158 6
            $alt_definitions = $matches['alt'] ?? null;
159
        }
160
161 6
        if ($alt_definitions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $alt_definitions of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
162
            // UNEXPECTED
163
            // this should not be possible, but just in case we will ever get here
164
            throw new ExtractorDefinitionBuilderException('Invalid varying definition');
165
        }
166
167 6
        return new VariableExtractorDefinition(...$alt);
168
    }
169
170 5
    protected function buildArrayDefinition(?ExtractorDefinitionInterface $definition, int $depth): ?ExtractorDefinitionInterface
171
    {
172 5
        while ($depth) {
173 5
            $depth--;
174
            // arrayed definition
175 5
            $definition = new PlainExtractorDefinition('[]', $definition);
176
        }
177
178 5
        return $definition;
179
    }
180
181 17
    private function getArrDepth(array $matches): int
182
    {
183 17
        if (!isset($matches['arr']) || '' === $matches['arr']) {
184 13
            return 0;
185
        }
186
187 6
        return substr_count($matches['arr'], '[');
188
    }
189
190 17
    private function hasGrops(array $matches): bool
191
    {
192 17
        return isset($matches['group']) && '' !== $matches['group'];
193
    }
194
}
195