Completed
Push — master ( 0c176f...c02018 )
by Bogdan
05:40
created

ExtractorDefinitionBuilder::buildExtractor()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.1666

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 10
cts 12
cp 0.8333
rs 8.439
c 0
b 0
f 0
cc 6
eloc 13
nc 16
nop 4
crap 6.1666
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
        (?<name>
33
            [-_\w]*
34
        )
35
        (?:
36
            \s*
37
            \(
38
            \s*
39
            (?<param>
40
                (?-3)*
41
                |
42
                [\w\\\\]+
43
            )
44
            \s*
45
            \)
46
        )?
47
        (?:
48
            \s*
49
            \|
50
            \s*
51
            (?<alt>(?-4))
52
        )?
53
        (?:
54
            \s*
55
            (?<arr>\[\s*\])
56
            \s*
57
        )?
58
    )
59
    $
60
    /xi';
61
62
    protected $type_regexp2 = '/
63
    ^
64
        (?:((\w+\b(?:\(.*\))?(?:\s*\[\s*\])?)(?:\s*\|\s*(?-1))*))
65
        |
66
        (?:\(\s*(?-2)\s*\)(?:\s*\[\s*\])?)
67
        |
68
        (\[\s*\])
69
    $
70
    /xi';
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 8
    public function build(string $definition): ExtractorDefinitionInterface
76
    {
77 8
        $definition = trim($definition);
78
79 8
        if (!$definition) {
80 1
            throw new ExtractorDefinitionBuilderException('Definition must be non-empty string');
81
        }
82
83 7
        if (preg_match($this->type_regexp, $definition, $matches)) {
84 6
            return $this->buildExtractor($matches['name'], $matches['param'] ?? null, $matches['alt'] ?? null, isset($matches['arr']));
85
        }
86
87 1
        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
     *
95
     * @return ExtractorDefinitionInterface
96
     * @throws ExtractorDefinitionBuilderException
97
     */
98 6
    protected function buildExtractor(string $name, ?string $param, ?string $alt_definitions, bool $is_arr): ExtractorDefinitionInterface
99
    {
100 6
        $next = null;
101
102 6
        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...
103 3
            $next = $this->buildExtractor($matches['name'], $matches['param'] ?? null, $matches['alt'] ?? null, isset($matches['arr']));
104
        }
105
106 6
        if ($name) {
107 6
            $definition = new PlainExtractorDefinition($name, $next);
108
        } else {
109
            $definition = $next;
110
        }
111
112 6
        if ($is_arr) {
113
            // arrayed definition
114
            $definition = new PlainExtractorDefinition('[]', $definition);
115
        }
116
117 6
        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...
118 2
            $definition = $this->buildVariableDefinition($definition, $alt_definitions);
0 ignored issues
show
Documentation introduced by
$definition is of type object<Pinepain\JsSandbo...finitionInterface>|null, but the function expects a object<Pinepain\JsSandbo...torDefinitionInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
119
        }
120
121 6
        return $definition;
122
    }
123
124
    /**
125
     * @param PlainExtractorDefinitionInterface $definition
126
     * @param string                            $alt_definitions
127
     *
128
     * @return VariableExtractorDefinition
129
     * @throws ExtractorDefinitionBuilderException
130
     */
131 2
    protected function buildVariableDefinition(PlainExtractorDefinitionInterface $definition, string $alt_definitions): VariableExtractorDefinition
132
    {
133 2
        $alt = [$definition];
134
135 2
        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...
136
            // build alt
137 2
            $alt[] = $this->buildExtractor($matches['name'], $matches['param'] ?? null, null, false);
138
139 2
            $alt_definitions = $matches['alt'] ?? null;
140
        }
141
142 2
        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...
143
            // UNEXPECTED
144
            // this should not be possible, but just in case we will ever get here
145
            throw new ExtractorDefinitionBuilderException('Invalid varying definition');
146
        }
147
148 2
        return new VariableExtractorDefinition(...$alt);
149
    }
150
151
}
152