Passed
Push — master ( 373c52...ed53b1 )
by Thorsten
03:30
created

ValuePathParser::consume()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * This file is part of the daikon-cqrs/entity project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace Daikon\Entity\Entity\Path;
12
13
use Daikon\Entity\Exception\InvalidPath;
14
use JMS\Parser\AbstractParser;
15
use JMS\Parser\SimpleLexer;
16
17
final class ValuePathParser extends AbstractParser
18
{
19
    /**
20
     * @var int
21
     */
22
    private const T_ATTRIBUTE = 1;
23
24
    /**
25
     * @var int
26
     */
27
    private const T_POSITION = 2;
28
29
    /**
30
     * @var int
31
     */
32
    private const T_COMPONENT_SEP = 3;
33
34
    /**
35
     * @var int
36
     */
37
    private const T_PART_SEP = 4;
38
39
    /**
40
     * @var string
41
     */
42
    private const TOKEN_REGEX = <<<REGEX
43
/
44
    # type identifier which refers to an attribute
45
    ([a-zA-Z_]+)
46
47
    # value position
48
    |(\d+)
49
50
    # value-path-component separator. the two components of a value-path-part being attribute and position.
51
    |(\.)
52
53
    # value-path separator
54
    |(\-)
55
/x
56
REGEX;
57
58
    /**
59
     * @var string[]
60
     */
61
    private const TOKEN_MAP = [
62
        0 => 'T_UNKNOWN',
63
        1 => 'T_ATTRIBUTE',
64
        2 => 'T_POSITION',
65
        3 => 'T_PART_SEP'
66
    ];
67
68
    /**
69
     * @return ValuePathParser
70
     */
71
    public static function create(): ValuePathParser
72
    {
73 6
        $mapToken = function (string $token): array {
74
            switch ($token) {
75 6
                case '.':
76 4
                    return [ self::T_COMPONENT_SEP, $token ];
77 6
                case '-':
78 4
                    return [ self::T_PART_SEP, $token ];
79
                default:
80 6
                    return is_numeric($token)
81 5
                        ? [ self::T_POSITION, (int)$token ]
82 6
                        : [ self::T_ATTRIBUTE, $token ];
83
            }
84
        };
85 14
        $lexer = new SimpleLexer(self::TOKEN_REGEX, self::TOKEN_MAP, $mapToken);
86 14
        return new ValuePathParser($lexer);
87
    }
88
89
    /**
90
     * @param string $path
91
     * @param string $context
92
     *
93
     * @return ValuePath
94
     */
95 6
    public function parse($path, $context = null): ValuePath
96
    {
97 6
        return parent::parse($path, $context);
98
    }
99
100
    /**
101
     * @return ValuePath
102
     */
103 6
    public function parseInternal(): ValuePath
104
    {
105 6
        $valuePathParts = [];
106 6
        while ($valuePathPart = $this->consume()) {
107 5
            $valuePathParts[] = $valuePathPart;
108
        }
109 5
        return new ValuePath($valuePathParts);
110
    }
111
112
    /**
113
     * @return null|ValuePathPart
114
     */
115 6
    private function consume(): ?ValuePathPart
116
    {
117 6
        $this->eatSeparator();
118 6
        $attribute = $this->parseAttribute();
119 5
        if (is_null($attribute)) {
120 5
            return null;
121
        }
122 5
        return new ValuePathPart($attribute, $this->parsePosition());
123
    }
124
125 6
    private function eatSeparator()
126
    {
127 6
        if ($this->lexer->isNext(self::T_PART_SEP)) {
128 3
            $this->match(self::T_PART_SEP);
129
        }
130 6
    }
131
132 6
    private function parseAttribute(): ?string
133
    {
134 6
        if (!$this->lexer->isNext(self::T_ATTRIBUTE)) {
135 6
            if ($this->lexer->next !== null) {
136 1
                throw new InvalidPath('Expecting T_TYPE at the beginning of a new path-part.');
137
            }
138 5
            return null;
139
        }
140 5
        return $this->match(self::T_ATTRIBUTE);
141
    }
142
143 5
    private function parsePosition(): int
144
    {
145 5
        if ($this->lexer->isNext(self::T_COMPONENT_SEP)) {
146 4
            $this->match(self::T_COMPONENT_SEP);
147 4
            return $this->match(self::T_POSITION);
148
        }
149 4
        return -1;
150
    }
151
}
152