Completed
Push — master ( 4964c5...ed9627 )
by Matt
10s
created

Pointer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace League\JsonReference;
4
5
use League\JsonReference\Pointer\InvalidPointerException;
6
use League\JsonReference\Pointer\NonExistentValue;
7
8
/**
9
 * A simple JSON Pointer implementation that can traverse
10
 * an object resulting from a json_decode() call.
11
 *
12
 * @see https://tools.ietf.org/html/rfc6901
13
 */
14
final class Pointer
15
{
16
    /**
17
     * @var object|array
18
     */
19
    private $json;
20
21
    /**
22
     * @param object|array $json
23
     */
24 90
    public function __construct(&$json)
25
    {
26 90
        $this->json = &$json;
27 90
    }
28
29
    /**
30
     * @param string $pointer
31
     *
32
     * @return mixed
33
     *
34
     * @throws InvalidPointerException
35
     */
36 58
    public function get($pointer)
37
    {
38 58
        $value = $this->traverse($pointer);
39
40 56
        if ($value instanceof NonExistentValue) {
41 6
            throw InvalidPointerException::nonexistentValue($value->getValue());
42
        }
43
44 50
        return $value;
45
    }
46
47
    /**
48
     * @param string $pointer
49
     *
50
     * @return bool
51
     */
52 54
    public function has($pointer)
53
    {
54 54
        return !$this->traverse($pointer) instanceof NonExistentValue;
55
    }
56
57
    /**
58
     * @param string $pointer
59
     * @param mixed  $data
60
     *
61
     * @return void
62
     *
63
     * @throws InvalidPointerException
64
     * @throws \InvalidArgumentException
65
     *
66
     */
67 58
    public function set($pointer, $data)
68
    {
69 58
        if ($pointer === '') {
70 2
            throw new \InvalidArgumentException('Cannot replace the object with set.');
71
        }
72
73 56
        $pointer = $this->parse($pointer);
74 56
        $replace = array_pop($pointer);
75 56
        $target  = &$this->getTarget($pointer);
76
77 56
        if (is_array($target)) {
78 8
            if ($replace === '-') {
79 2
                $target[] = $data;
80 1
            } else {
81 7
                $target[$replace] = $data;
82
            }
83 53
        } elseif (is_object($target)) {
84 48
            $target->$replace = $data;
85 24
        } else {
86 2
            throw InvalidPointerException::invalidTarget($target);
87
        }
88 54
    }
89
90
    /**
91
     * @param string $pointer
92
     *
93
     * @return void
94
     */
95 12
    public function remove($pointer)
96
    {
97 12
        if ($pointer === '') {
98
            throw new \InvalidArgumentException('Cannot remove the object.');
99
        }
100
101 12
        $pointer = $this->parse($pointer);
102 12
        $remove  = array_pop($pointer);
103 12
        $target  = &$this->getTarget($pointer);
104
105 12
        if (is_array($target)) {
106 2
            unset($target[$remove]);
107 11
        } elseif (is_object($target)) {
108 10
            unset($target->$remove);
109 5
        } else {
110
            throw InvalidPointerException::invalidTarget($target);
111
        }
112 12
    }
113
114
    /**
115
     * @param array $pointer
116
     *
117
     * @return mixed
118
     */
119 64
    private function &getTarget(array $pointer)
120
    {
121 64
        $target = &$this->json;
122
123 64
        foreach ($pointer as $segment) {
124 46
            if (is_array($target)) {
125 6
                $target =& $target[$segment];
126 3
            } else {
127 46
                $target =& $target->$segment;
128
            }
129 32
        }
130
131 64
        return $target;
132
    }
133
134
    /**
135
     * Returns the value referenced by the pointer or a NonExistentValue if the value does not exist.
136
     *
137
     * @param string $pointer The pointer
138
     *
139
     * @return mixed
140
     */
141 68
    private function traverse($pointer)
142
    {
143 68
        $pointer = $this->parse($pointer);
144 66
        $json    = $this->json;
145
146 66
        foreach ($pointer as $segment) {
147 66
            if ($json instanceof Reference) {
148
                if (!$json->has($segment)) {
149
                    return new NonExistentValue($segment);
150
                }
151
                $json = $json->get($segment);
152 66
            } elseif (is_object($json)) {
153
                // who does this?
154 66
                if ($segment === '' && property_exists($json, '_empty_')) {
155 4
                    $segment = '_empty_';
156 2
                }
157 66
                if (!property_exists($json, $segment)) {
158 6
                    return new NonExistentValue($segment);
159
                }
160 64
                $json = $json->$segment;
161 38
            } elseif (is_array($json)) {
162 12
                if (!array_key_exists($segment, $json)) {
163 2
                    return new NonExistentValue($segment);
164
                }
165 10
                $json = $json[$segment];
166 5
            } else {
167 33
                return new NonExistentValue($segment);
168
            }
169 32
        }
170
171 60
        return $json;
172
    }
173
174
    /**
175
     * Parses a JSON Pointer as defined in the specification.
176
     * @see https://tools.ietf.org/html/rfc6901#section-4
177
     *
178
     * @param string $pointer
179
     *
180
     * @return array
181
     *
182
     * @throws InvalidPointerException
183
     */
184 86
    private function parse($pointer)
185
    {
186 86
        if (!is_string($pointer)) {
187 2
            throw InvalidPointerException::invalidType(gettype($pointer));
188
        }
189
190 84
        if (!isset($pointer[0])) {
191 2
            return [];
192
        }
193
194
        // If the pointer is a url fragment, it needs to be url decoded.
195 84
        if ($pointer[0] === '#') {
196 40
            $pointer = urldecode(substr($pointer, 1));
197 20
        }
198
199
        // For convenience add the beginning slash if it's missing.
200 84
        if (isset($pointer[0]) && $pointer[0] !== '/') {
201 26
            $pointer = '/' . $pointer;
202 13
        }
203
204 84
        $pointer = array_slice(explode('/', $pointer), 1);
205 84
        return str_replace(['~1', '~0'], ['/', '~'], $pointer);
206
    }
207
}
208