Completed
Pull Request — master (#4)
by Matt
02:12
created

Pointer::traverse()   D

Complexity

Conditions 10
Paths 10

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 10.2918

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 10
nop 2
dl 0
loc 29
ccs 18
cts 21
cp 0.8571
crap 10.2918
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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->getRaw($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 mixed
51
     */
52 68
    public function getRaw($pointer)
53
    {
54 68
        $pointer = $this->parse($pointer);
55
56 66
        return $this->traverse($this->json, $pointer);
57
    }
58
59
    /**
60
     * @param string $pointer
61
     *
62
     * @return bool
63
     */
64 54
    public function has($pointer)
65
    {
66 54
        return !$this->getRaw($pointer) instanceof NonExistentValue;
67
    }
68
69
    /**
70
     * @param string $pointer
71
     * @param mixed  $data
72
     *
73
     * @return void
74
     *
75
     * @throws InvalidPointerException
76
     * @throws \InvalidArgumentException
77
     *
78
     */
79 58
    public function set($pointer, $data)
80
    {
81 58
        if ($pointer === '') {
82 2
            throw new \InvalidArgumentException('Cannot replace the object with set.');
83
        }
84
85 56
        $pointer = $this->parse($pointer);
86 56
        $replace = array_pop($pointer);
87 56
        $target  = &$this->getTarget($pointer);
88
89 56
        if (is_array($target)) {
90 8
            if ($replace === '-') {
91 2
                $target[] = $data;
92 1
            } else {
93 7
                $target[$replace] = $data;
94
            }
95 53
        } elseif (is_object($target)) {
96 48
            $target->$replace = $data;
97 24
        } else {
98 2
            throw InvalidPointerException::invalidTarget($target);
99
        }
100 54
    }
101
102
    /**
103
     * @param string $pointer
104
     *
105
     * @return void
106
     */
107 12
    public function remove($pointer)
108
    {
109 12
        if ($pointer === '') {
110
            throw new \InvalidArgumentException('Cannot remove the object.');
111
        }
112
113 12
        $pointer = $this->parse($pointer);
114 12
        $remove  = array_pop($pointer);
115 12
        $target  = &$this->getTarget($pointer);
116
117 12
        if (is_array($target)) {
118 2
            unset($target[$remove]);
119 11
        } elseif (is_object($target)) {
120 10
            unset($target->$remove);
121 5
        } else {
122
            throw InvalidPointerException::invalidTarget($target);
123
        }
124 12
    }
125
126
    /**
127
     * @param array $pointer
128
     *
129
     * @return mixed
130
     */
131 64
    private function &getTarget(array $pointer)
132
    {
133 64
        $target = &$this->json;
134
135 64
        foreach ($pointer as $segment) {
136 46
            if (is_array($target)) {
137 6
                $target =& $target[$segment];
138 3
            } else {
139 46
                $target =& $target->$segment;
140
            }
141 32
        }
142
143 64
        return $target;
144
    }
145
146
    /**
147
     * @param mixed $json    The result of a json_decode call or a portion of it.
148
     * @param array $pointer The parsed pointer
149
     *
150
     * @return mixed
151
     */
152 66
    private function traverse($json, $pointer)
153
    {
154 66
        foreach ($pointer as $segment) {
155 66
            if ($json instanceof Reference) {
156
                if (!$json->has($segment)) {
157
                    return new NonExistentValue($segment);
158
                }
159
                $json = $json->get($segment);
160 66
            } elseif (is_object($json)) {
161
                // who does this?
162 66
                if ($segment === '' && property_exists($json, '_empty_')) {
163 4
                    $segment = '_empty_';
164 2
                }
165 66
                if (!property_exists($json, $segment)) {
166 6
                    return new NonExistentValue($segment);
167
                }
168 64
                $json = $json->$segment;
169 38
            } elseif (is_array($json)) {
170 12
                if (!array_key_exists($segment, $json)) {
171 2
                    return new NonExistentValue($segment);
172
                }
173 10
                $json = $json[$segment];
174 5
            } else {
175 33
                return new NonExistentValue($segment);
176
            }
177 32
        }
178
179 60
        return $json;
180
    }
181
182
    /**
183
     * Parses a JSON Pointer as defined in the specification.
184
     * @see https://tools.ietf.org/html/rfc6901#section-4
185
     *
186
     * @param string $pointer
187
     *
188
     * @return array
189
     *
190
     * @throws InvalidPointerException
191
     */
192 86
    private function parse($pointer)
193
    {
194 86
        if (!is_string($pointer)) {
195 2
            throw InvalidPointerException::invalidType(gettype($pointer));
196
        }
197
198 84
        if (!isset($pointer[0])) {
199 2
            return [];
200
        }
201
202
        // If the pointer is a url fragment, it needs to be url decoded.
203 84
        if ($pointer[0] === '#') {
204 40
            $pointer = urldecode(substr($pointer, 1));
205 20
        }
206
207
        // For convenience add the beginning slash if it's missing.
208 84
        if (isset($pointer[0]) && $pointer[0] !== '/') {
209 26
            $pointer = '/' . $pointer;
210 13
        }
211
212 84
        $pointer = array_slice(explode('/', $pointer), 1);
213 84
        return str_replace(['~1', '~0'], ['/', '~'], $pointer);
214
    }
215
}
216