PointerEvaluator   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Test Coverage

Coverage 85.25%

Importance

Changes 0
Metric Value
wmc 26
eloc 62
dl 0
loc 167
c 0
b 0
f 0
ccs 52
cts 61
cp 0.8525
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getRootNode() 0 3 1
A evaluatePointerPath() 0 21 4
A getValidArrayIndex() 0 8 2
A getArrayIndexFromPathComponent() 0 7 2
A evaluatePointer() 0 15 4
A validateRelativePointerContextNode() 0 12 4
A __construct() 0 13 3
A getObjectPropertyFromPathComponent() 0 7 2
A evaluateRelativePointer() 0 17 4
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\Jom;
4
5
use DaveRandom\Jom\Exceptions\InvalidKeyException;
6
use DaveRandom\Jom\Exceptions\InvalidPointerException;
7
use DaveRandom\Jom\Exceptions\InvalidReferenceNodeException;
8
use DaveRandom\Jom\Exceptions\PointerReferenceNotFoundException;
9
10
final class PointerEvaluator
11
{
12
    private const NODE_TYPE_NAMES  = [
13
        BooleanNode::class => 'boolean',
14
        NumberNode::class => 'number',
15
        StringNode::class => 'string',
16
        NullNode::class => 'null',
17
    ];
18
19
    private $root;
20
21
    /**
22
     * @throws PointerReferenceNotFoundException
23
     */
24 4
    private function getValidArrayIndex(string $index, Pointer $pointer, int $level): int
25
    {
26 4
        if (\preg_match('/^(?:0|[1-9][0-9]*)$/', $index)) {
27 4
            return (int)$index;
28
        }
29
30
        throw new PointerReferenceNotFoundException(
31
            "Array member must be referenced by integer index without leading zero", $pointer, $level
32
        );
33
    }
34
35
    /**
36
     * @throws PointerReferenceNotFoundException
37
     */
38 4
    private function getArrayIndexFromPathComponent(Pointer $pointer, ArrayNode $node, string $index, int $level)
39
    {
40
        try {
41 4
            return $node->item($this->getValidArrayIndex($index, $pointer, $level));
42 1
        } catch (InvalidKeyException $e) {
43 1
            throw new PointerReferenceNotFoundException(
44 1
                "The referenced index does not exist", $pointer, $level, $e
45
            );
46
        }
47
    }
48
49
    /**
50
     * @throws PointerReferenceNotFoundException
51
     */
52 14
    private function getObjectPropertyFromPathComponent(Pointer $pointer, ObjectNode $node, string $name, int $level)
53
    {
54
        try {
55 14
            return $node->getProperty($name);
56 2
        } catch (InvalidKeyException $e) {
57 2
            throw new PointerReferenceNotFoundException(
58 2
                'The referenced property does not exist', $pointer, $level, $e
59
            );
60
        }
61
    }
62
63
    /**
64
     * @throws PointerReferenceNotFoundException
65
     */
66 37
    private function evaluatePointerPath(Pointer $pointer, Node $current): Node
67
    {
68 37
        foreach ($pointer->getPath() as $level => $component) {
69 16
            if ($current instanceof ObjectNode) {
70 14
                $current = $this->getObjectPropertyFromPathComponent($pointer, $current, $component, $level);
71 13
                continue;
72
            }
73
74 4
            if ($current instanceof ArrayNode) {
75 4
                $current = $this->getArrayIndexFromPathComponent($pointer, $current, $component, $level);
76 3
                continue;
77
            }
78
79
            $typeName = self::NODE_TYPE_NAMES[\get_class($current)] ?? 'unknown type';
80
81
            throw new PointerReferenceNotFoundException(
82
                "Expecting object or array, got {$typeName}", $pointer, $level
83
            );
84
        }
85
86 34
        return $current;
87
    }
88
89
    /**
90
     * @throws InvalidReferenceNodeException
91
     */
92 36
    private function validateRelativePointerContextNode(Pointer $pointer, Node $context): void
93
    {
94 36
        if ($context->getOwnerDocument() !== $this->root->getOwnerDocument()) {
95
            throw new InvalidReferenceNodeException(
96
                'Context node for relative pointer evaluation does not belong to the same document'
97
                . ' as the evaluator root node'
98
            );
99
        }
100
101 36
        if ($pointer->getRelativeLevels() > 0 && !$this->root->containsChild($context)) {
102
            throw new InvalidReferenceNodeException(
103
                'Context node for relative pointer evaluation is not a child of the root node'
104
            );
105
        }
106
    }
107
108
    /**
109
     * @throws PointerReferenceNotFoundException
110
     * @throws InvalidReferenceNodeException
111
     */
112 42
    private function evaluateRelativePointer(Pointer $pointer, Node $current): Node
113
    {
114 42
        if ($current !== $this->root) {
115 36
            $this->validateRelativePointerContextNode($pointer, $current);
116
        }
117
118 42
        for ($i = 0, $levels = $pointer->getRelativeLevels(); $i < $levels; $i++) {
119 30
            if (($current ?? $this->root) === $this->root) {
120 12
                throw new PointerReferenceNotFoundException(
121 12
                    "Relative pointer prefix overflows context node nesting level {$i}", $pointer
122
                );
123
            }
124
125 27
            $current = $current->getParent();
126
        }
127
128 30
        return $this->evaluatePointerPath($pointer, $current);
129
    }
130
131
    /**
132
     * @param Node|Document $root
133
     * @throws InvalidReferenceNodeException
134
     */
135 52
    public function __construct($root)
136
    {
137 52
        if ($root instanceof Document) {
138 1
            $root = $root->getRootNode();
139
        }
140
141 52
        if (!($root instanceof Node)) {
142 1
            throw new InvalidReferenceNodeException(
143 1
                'Pointer evaluator root node must be instance of ' . Node::class . ' or ' . Document::class
144
            );
145
        }
146
147 51
        $this->root = $root;
148
    }
149
150 2
    public function getRootNode(): Node
151
    {
152 2
        return $this->root;
153
    }
154
155
    /**
156
     * @param Pointer|string $pointer
157
     * @return Node|int|string
158
     * @throws InvalidPointerException
159
     * @throws InvalidReferenceNodeException
160
     * @throws PointerReferenceNotFoundException
161
     */
162 49
    public function evaluatePointer($pointer, ?Node $context = null)
163
    {
164 49
        if (!($pointer instanceof Pointer)) {
165 49
            $pointer = Pointer::createFromString((string)$pointer);
166
        }
167
168 49
        if (!$pointer->isRelative()) {
169 7
            return $this->evaluatePointerPath($pointer, $this->root);
170
        }
171
172 42
        $target = $this->evaluateRelativePointer($pointer, $context ?? $this->root);
173
174 30
        return $pointer->isKeyLookup()
175 10
            ? $target->getKey()
176 30
            : $target;
177
    }
178
}
179