Completed
Push — master ( 8230d8...05e612 )
by Viacheslav
9s
created

JsonPointer::arrayKeyExists()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 2
crap 4
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 21
    public static function escapeSegment($key, $isURIFragmentId = false)
29
    {
30 21
        if ($isURIFragmentId) {
31 3
            return str_replace(array('%7E', '%2F'), array('~0', '~1'), urlencode($key));
32
        } else {
33 19
            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 69
    public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIVE_KEY_CREATION)
98
    {
99 69
        $ref = &$holder;
100 69
        while (null !== $key = array_shift($pathItems)) {
101 65
            if ($ref instanceof \stdClass) {
102 45
                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 45
                if ($flags & self::RECURSIVE_KEY_CREATION) {
108 9
                    $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 42
                        $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 39
                $intKey = filter_var($key, FILTER_VALIDATE_INT);
118 39
                if ($ref === null && (false === $intKey || $intKey !== 0)) {
119 11
                    $key = (string)$key;
120 11
                    if ($flags & self::RECURSIVE_KEY_CREATION) {
121 11
                        $ref = new \stdClass();
122 11
                        $ref = &$ref->{$key};
123
                    } else {
124 11
                        throw new Exception('Non-existent path item: ' . $key);
125
                    }
126 36
                } elseif ([] === $ref && 0 === ($flags & self::STRICT_MODE) && false === $intKey && '-' !== $key) {
127 1
                    $ref = new \stdClass();
128 1
                    $ref = &$ref->{$key};
129
                } else {
130 36
                    if ($flags & self::RECURSIVE_KEY_CREATION && $ref === null) $ref = array();
131 36
                    if ('-' === $key) {
132 4
                        $ref = &$ref[];
133
                    } else {
134 33
                        if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
135 5
                            array_splice($ref, $key, 0, array($value));
136
                        }
137 33
                        if (false === $intKey) {
138 3
                            throw new Exception('Invalid key for array operation');
139
                        }
140 30
                        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 28
                        } elseif ($intKey < 0) {
143 1
                            throw new Exception('Negative index');
144
                        }
145
146 27
                        $ref = &$ref[$intKey];
147
                    }
148
                }
149
            }
150
        }
151 61
        if ($ref !== null && $flags & self::SKIP_IF_ISSET) {
152 1
            return;
153
        }
154 61
        $ref = $value;
155 61
    }
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 8
                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 44
    public static function get($holder, $pathItems)
190
    {
191 44
        $ref = $holder;
192 44
        while (null !== $key = array_shift($pathItems)) {
193 43
            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 22
            } 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
            } else {
212
                throw new Exception('Key not found: ' . $key);
213
            }
214
        }
215 38
        return $ref;
216
    }
217
218
    /**
219
     * @param mixed $holder
220
     * @param string $pointer
221
     * @return bool|mixed
222
     * @throws Exception
223
     */
224 1
    public static function getByPointer($holder, $pointer)
225
    {
226 1
        return self::get($holder, self::splitPath($pointer));
227
    }
228
229
    /**
230
     * @param mixed $holder
231
     * @param string[] $pathItems
232
     * @return mixed
233
     * @throws Exception
234
     */
235 36
    public static function remove(&$holder, $pathItems)
236
    {
237 36
        $ref = &$holder;
238 36
        while (null !== $key = array_shift($pathItems)) {
239 35
            $parent = &$ref;
240 35
            $refKey = $key;
241 35
            if ($ref instanceof \stdClass) {
242 23
                if (property_exists($ref, $key)) {
243 22
                    $ref = &$ref->$key;
244
                } else {
245 23
                    throw new Exception('Key not found: ' . $key);
246
                }
247
            } else {
248 22
                if (array_key_exists($key, $ref)) {
249 19
                    $ref = &$ref[$key];
250
                } else {
251 3
                    throw new Exception('Key not found: ' . $key);
252
                }
253
            }
254
        }
255
256 32
        if (isset($parent) && isset($refKey)) {
257 31
            if ($parent instanceof \stdClass) {
258 19
                unset($parent->$refKey);
259
            } else {
260 15
                unset($parent[$refKey]);
261 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...
262 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...
263
                }
264
            }
265
        }
266 32
        return $ref;
267
    }
268
}
269