Completed
Push — master ( dadb65...1282c4 )
by Viacheslav
13s
created

JsonPointer::splitPathURIFragment()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
c 0
b 0
f 0
ccs 6
cts 6
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Swaggest\JsonDiff;
4
5
6
class JsonPointer
7
{
8
    /**
9
     * Create intermediate keys if they don't exist
10
     */
11
    const RECURSIVE_KEY_CREATION = 1;
12
13
    /**
14
     * Disallow converting empty array to object for key creation
15
     */
16
    const STRICT_MODE = 2;
17
18
    /**
19
     * Skip action if holder already has a non-null value at path
20
     */
21
    const SKIP_IF_ISSET = 4;
22
23
    /**
24
     * @param string $key
25
     * @param bool $isURIFragmentId
26
     * @return string
27
     */
28 40
    public static function escapeSegment($key, $isURIFragmentId = false)
29
    {
30 40
        if ($isURIFragmentId) {
31 3
            return str_replace(array('%7E', '%2F'), array('~0', '~1'), urlencode($key));
32
        } else {
33 38
            return str_replace(array('~', '/'), array('~0', '~1'), $key);
34
        }
35
    }
36
37
    /**
38
     * @param string[] $pathItems
39
     * @param bool $isURIFragmentId
40
     * @return string
41
     */
42 1
    public static function buildPath(array $pathItems, $isURIFragmentId = false)
43
    {
44 1
        $result = $isURIFragmentId ? '#' : '';
45 1
        foreach ($pathItems as $pathItem) {
46 1
            $result .= '/' . self::escapeSegment($pathItem, $isURIFragmentId);
47
        }
48 1
        return $result;
49
    }
50
51
    /**
52
     * @param string $path
53
     * @return string[]
54
     * @throws Exception
55
     */
56 95
    public static function splitPath($path)
57
    {
58 95
        $pathItems = explode('/', $path);
59 95
        $first = array_shift($pathItems);
60 95
        if ($first === '#') {
61 1
            return self::splitPathURIFragment($pathItems);
62
        } else {
63 95
            if ($first !== '') {
64
                throw new Exception('Path must start with "/": ' . $path);
65
            }
66 95
            return self::splitPathJsonString($pathItems);
67
        }
68
    }
69
70 1
    private static function splitPathURIFragment(array $pathItems)
71
    {
72 1
        $result = array();
73 1
        foreach ($pathItems as $key) {
74 1
            $key = str_replace(array('~1', '~0'), array('/', '~'), urldecode($key));
75 1
            $result[] = $key;
76
        }
77 1
        return $result;
78
    }
79
80 95
    private static function splitPathJsonString(array $pathItems)
81
    {
82 95
        $result = array();
83 95
        foreach ($pathItems as $key) {
84 91
            $key = str_replace(array('~1', '~0'), array('/', '~'), $key);
85 91
            $result[] = $key;
86
        }
87 95
        return $result;
88
    }
89
90
    /**
91
     * @param mixed $holder
92
     * @param string[] $pathItems
93
     * @param mixed $value
94
     * @param int $flags
95
     * @throws Exception
96
     */
97 91
    public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIVE_KEY_CREATION)
98
    {
99 91
        $ref = &$holder;
100 91
        while (null !== $key = array_shift($pathItems)) {
101 83
            if ($ref instanceof \stdClass || is_object($ref)) {
102 47
                if (PHP_VERSION_ID < 71000 && '' === $key) {
103
                    throw new Exception('Empty property name is not supported by PHP <7.1',
104
                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
105
                }
106
107 47
                if ($flags & self::RECURSIVE_KEY_CREATION) {
108 12
                    $ref = &$ref->$key;
109
                } else {
110 40
                    if (!isset($ref->$key) && count($pathItems)) {
111 3
                        throw new Exception('Non-existent path item: ' . $key);
112
                    } else {
113 44
                        $ref = &$ref->$key;
114
                    }
115
                }
116
            } else { // null or array
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
117 56
                $intKey = filter_var($key, FILTER_VALIDATE_INT);
118 56
                if ($ref === null && (false === $intKey || $intKey !== 0)) {
119 26
                    $key = (string)$key;
120 26
                    if ($flags & self::RECURSIVE_KEY_CREATION) {
121 26
                        $ref = new \stdClass();
122 26
                        $ref = &$ref->{$key};
123
                    } else {
124 26
                        throw new Exception('Non-existent path item: ' . $key);
125
                    }
126 42
                } elseif ([] === $ref && 0 === ($flags & self::STRICT_MODE) && false === $intKey && '-' !== $key) {
127 1
                    $ref = new \stdClass();
128 1
                    $ref = &$ref->{$key};
129
                } else {
130 42
                    if ($flags & self::RECURSIVE_KEY_CREATION && $ref === null) $ref = array();
131 42
                    if ('-' === $key) {
132 4
                        $ref = &$ref[];
133
                    } else {
134 39
                        if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
135 8
                            array_splice($ref, $key, 0, array($value));
136
                        }
137 39
                        if (false === $intKey) {
138 3
                            throw new Exception('Invalid key for array operation');
139
                        }
140 36
                        if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) {
141 2
                            throw new Exception('Index is greater than number of items in array');
142 34
                        } elseif ($intKey < 0) {
143 1
                            throw new Exception('Negative index');
144
                        }
145
146 33
                        $ref = &$ref[$intKey];
147
                    }
148
                }
149
            }
150
        }
151 83
        if ($ref !== null && $flags & self::SKIP_IF_ISSET) {
152 1
            return;
153
        }
154 83
        $ref = $value;
155 83
    }
156
157 43
    private static function arrayKeyExists($key, array $a)
158
    {
159 43
        if (array_key_exists($key, $a)) {
160 39
            return true;
161
        }
162 8
        $key = (string)$key;
163 8
        foreach ($a as $k => $v) {
164 8
            if ((string)$k === $key) {
165
                return true;
166
            }
167
        }
168 8
        return false;
169
    }
170
171 31
    private static function arrayGet($key, array $a)
172
    {
173 31
        $key = (string)$key;
174 31
        foreach ($a as $k => $v) {
175 31
            if ((string)$k === $key) {
176 31
                return $v;
177
            }
178
        }
179
        return false;
180
    }
181
182
183
    /**
184
     * @param mixed $holder
185
     * @param string[] $pathItems
186
     * @return bool|mixed
187
     * @throws Exception
188
     */
189 45
    public static function get($holder, $pathItems)
190
    {
191 45
        $ref = $holder;
192 45
        while (null !== $key = array_shift($pathItems)) {
193 44
            if ($ref instanceof \stdClass) {
194 32
                if (PHP_VERSION_ID < 71000 && '' === $key) {
195
                    throw new Exception('Empty property name is not supported by PHP <7.1',
196
                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
197
                }
198
199 32
                $vars = (array)$ref;
200 32
                if (self::arrayKeyExists($key, $vars)) {
201 31
                    $ref = self::arrayGet($key, $vars);
202
                } else {
203 32
                    throw new Exception('Key not found: ' . $key);
204
                }
205 23
            } elseif (is_array($ref)) {
206 22
                if (self::arrayKeyExists($key, $ref)) {
207 17
                    $ref = $ref[$key];
208
                } else {
209 22
                    throw new Exception('Key not found: ' . $key);
210
                }
211 1
            } elseif (is_object($ref)) {
212 1
                if (isset($ref->$key)) {
213 1
                    $ref = $ref->$key;
214
                } else {
215 1
                    throw new Exception('Key not found: ' . $key);
216
                }
217
            } else {
218
                throw new Exception('Key not found: ' . $key);
219
            }
220
        }
221 39
        return $ref;
222
    }
223
224
    /**
225
     * @param mixed $holder
226
     * @param string $pointer
227
     * @return bool|mixed
228
     * @throws Exception
229
     */
230 1
    public static function getByPointer($holder, $pointer)
231
    {
232 1
        return self::get($holder, self::splitPath($pointer));
233
    }
234
235
    /**
236
     * @param mixed $holder
237
     * @param string[] $pathItems
238
     * @return mixed
239
     * @throws Exception
240
     */
241 37
    public static function remove(&$holder, $pathItems)
242
    {
243 37
        $ref = &$holder;
244 37
        while (null !== $key = array_shift($pathItems)) {
245 36
            $parent = &$ref;
246 36
            $refKey = $key;
247 36
            if ($ref instanceof \stdClass) {
248 23
                if (property_exists($ref, $key)) {
249 22
                    $ref = &$ref->$key;
250
                } else {
251 23
                    throw new Exception('Key not found: ' . $key);
252
                }
253 23
            } elseif (is_object($ref)) {
254 1
                if (isset($ref->$key)) {
255 1
                    $ref = &$ref->$key;
256
                } else {
257 1
                    throw new Exception('Key not found: ' . $key);
258
                }
259
            } else {
260 22
                if (array_key_exists($key, $ref)) {
261 19
                    $ref = &$ref[$key];
262
                } else {
263 3
                    throw new Exception('Key not found: ' . $key);
264
                }
265
            }
266
        }
267
268 33
        if (isset($parent) && isset($refKey)) {
269 32
            if ($parent instanceof \stdClass || is_object($parent)) {
270 20
                unset($parent->$refKey);
271
            } else {
272 15
                unset($parent[$refKey]);
273 15
                if ($refKey !== count($parent)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parent does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $refKey does not seem to be defined for all execution paths leading up to this point.
Loading history...
274 15
                    $parent = array_values($parent);
0 ignored issues
show
Unused Code introduced by
The assignment to $parent is dead and can be removed.
Loading history...
275
                }
276
            }
277
        }
278 33
        return $ref;
279
    }
280
}
281