Passed
Push — master ( 44b707...94925e )
by Viacheslav
03:07
created

JsonPointer::buildPath()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 4
nop 2
crap 3
1
<?php
2
3
namespace Swaggest\JsonDiff;
4
5
6
class JsonPointer
7
{
8
    /**
9
     * @param string $key
10
     * @param bool $isURIFragmentId
11
     * @return string
12
     */
13 20
    public static function escapeSegment($key, $isURIFragmentId = false)
14
    {
15 20
        if ($isURIFragmentId) {
16 3
            return str_replace(array('%7E', '%2F'), array('~0', '~1'), urlencode($key));
17
        } else {
18 18
            return str_replace(array('~', '/'), array('~0', '~1'), $key);
19
        }
20
    }
21
22
    /**
23
     * @param string[] $pathItems
24
     * @param bool $isURIFragmentId
25
     * @return string
26
     */
27 1
    public static function buildPath(array $pathItems, $isURIFragmentId = false)
28
    {
29 1
        $result = $isURIFragmentId ? '#' : '';
30 1
        foreach ($pathItems as $pathItem) {
31 1
            $result .= '/' . self::escapeSegment($pathItem, $isURIFragmentId);
32
        }
33 1
        return $result;
34
    }
35
36
    /**
37
     * @param string $path
38
     * @return string[]
39
     * @throws Exception
40
     */
41 94
    public static function splitPath($path)
42
    {
43 94
        $pathItems = explode('/', $path);
44 94
        $first = array_shift($pathItems);
45 94
        if ($first === '#') {
46 1
            return self::splitPathURIFragment($pathItems);
47
        } else {
48 94
            if ($first !== '') {
49
                throw new Exception('Path must start with "/": ' . $path);
50
            }
51 94
            return self::splitPathJsonString($pathItems);
52
        }
53
    }
54
55 1
    private static function splitPathURIFragment(array $pathItems)
56
    {
57 1
        $result = array();
58 1
        foreach ($pathItems as $key) {
59 1
            $key = str_replace(array('~1', '~0'), array('/', '~'), urldecode($key));
60 1
            $result[] = $key;
61
        }
62 1
        return $result;
63
    }
64
65 94
    private static function splitPathJsonString(array $pathItems)
66
    {
67 94
        $result = array();
68 94
        foreach ($pathItems as $key) {
69 90
            $key = str_replace(array('~1', '~0'), array('/', '~'), $key);
70 90
            $result[] = $key;
71
        }
72 94
        return $result;
73
    }
74
75
    /**
76
     * @param mixed $holder
77
     * @param string[] $pathItems
78
     * @param mixed $value
79
     * @param bool $recursively
80
     * @throws Exception
81
     */
82 68
    public static function add(&$holder, $pathItems, $value, $recursively = true)
83
    {
84 68
        $ref = &$holder;
85 68
        while (null !== $key = array_shift($pathItems)) {
86 64
            if ($ref instanceof \stdClass) {
87 44
                if (PHP_VERSION_ID < 71000 && '' === $key) {
88
                    throw new Exception('Empty property name is not supported by PHP <7.1',
89
                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
90
                }
91
92 44
                if ($recursively) {
93 9
                    $ref = &$ref->$key;
94
                } else {
95 38
                    if (!isset($ref->$key) && count($pathItems)) {
96 3
                        throw new Exception('Non-existent path item: ' . $key);
97
                    } else {
98 41
                        $ref = &$ref->$key;
99
                    }
100
                }
101
            } 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...
102 38
                $intKey = filter_var($key, FILTER_VALIDATE_INT);
103 38
                if ($ref === null && (false === $intKey || $intKey !== 0)) {
104 10
                    $key = (string)$key;
105 10
                    if ($recursively) {
106 10
                        $ref = new \stdClass();
107 10
                        $ref = &$ref->{$key};
108
                    } else {
109 10
                        throw new Exception('Non-existent path item: ' . $key);
110
                    }
111
                } else {
112 35
                    if ($recursively && $ref === null) $ref = array();
113 35
                    if ('-' === $key) {
114 4
                        $ref = &$ref[];
115
                    } else {
116 32
                        if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
117 5
                            array_splice($ref, $key, 0, array($value));
118
                        }
119 32
                        if (false === $intKey) {
120 2
                            throw new Exception('Invalid key for array operation');
121
                        }
122 30
                        if ($intKey > count($ref) && !$recursively) {
123 2
                            throw new Exception('Index is greater than number of items in array');
124 28
                        } elseif ($intKey < 0) {
125 1
                            throw new Exception('Negative index');
126
                        }
127
128 27
                        $ref = &$ref[$intKey];
129
                    }
130
                }
131
            }
132
        }
133 60
        $ref = $value;
134 60
    }
135
136 43
    private static function arrayKeyExists($key, array $a)
137
    {
138 43
        if (array_key_exists($key, $a)) {
139 39
            return true;
140
        }
141 8
        $key = (string)$key;
142 8
        foreach ($a as $k => $v) {
143 8
            if ((string)$k === $key) {
144 8
                return true;
145
            }
146
        }
147 8
        return false;
148
    }
149
150 31
    private static function arrayGet($key, array $a)
151
    {
152 31
        $key = (string)$key;
153 31
        foreach ($a as $k => $v) {
154 31
            if ((string)$k === $key) {
155 31
                return $v;
156
            }
157
        }
158
        return false;
159
    }
160
161
162
    /**
163
     * @param mixed $holder
164
     * @param string[] $pathItems
165
     * @return bool|mixed
166
     * @throws Exception
167
     */
168 44
    public static function get($holder, $pathItems)
169
    {
170 44
        $ref = $holder;
171 44
        while (null !== $key = array_shift($pathItems)) {
172 43
            if ($ref instanceof \stdClass) {
173 32
                if (PHP_VERSION_ID < 71000 && '' === $key) {
174
                    throw new Exception('Empty property name is not supported by PHP <7.1',
175
                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
176
                }
177
178 32
                $vars = (array)$ref;
179 32
                if (self::arrayKeyExists($key, $vars)) {
180 31
                    $ref = self::arrayGet($key, $vars);
181
                } else {
182 32
                    throw new Exception('Key not found: ' . $key);
183
                }
184 22
            } elseif (is_array($ref)) {
185 22
                if (self::arrayKeyExists($key, $ref)) {
186 17
                    $ref = $ref[$key];
187
                } else {
188 22
                    throw new Exception('Key not found: ' . $key);
189
                }
190
            } else {
191
                throw new Exception('Key not found: ' . $key);
192
            }
193
        }
194 38
        return $ref;
195
    }
196
197
    /**
198
     * @param mixed $holder
199
     * @param string $pointer
200
     * @return bool|mixed
201
     * @throws Exception
202
     */
203 1
    public static function getByPointer($holder, $pointer)
204
    {
205 1
        return self::get($holder, self::splitPath($pointer));
206
    }
207
208
    /**
209
     * @param mixed $holder
210
     * @param string[] $pathItems
211
     * @return mixed
212
     * @throws Exception
213
     */
214 36
    public static function remove(&$holder, $pathItems)
215
    {
216 36
        $ref = &$holder;
217 36
        while (null !== $key = array_shift($pathItems)) {
218 35
            $parent = &$ref;
219 35
            $refKey = $key;
220 35
            if ($ref instanceof \stdClass) {
221 23
                if (property_exists($ref, $key)) {
222 22
                    $ref = &$ref->$key;
223
                } else {
224 23
                    throw new Exception('Key not found: ' . $key);
225
                }
226
            } else {
227 22
                if (array_key_exists($key, $ref)) {
228 19
                    $ref = &$ref[$key];
229
                } else {
230 3
                    throw new Exception('Key not found: ' . $key);
231
                }
232
            }
233
        }
234
235 32
        if (isset($parent) && isset($refKey)) {
236 31
            if ($parent instanceof \stdClass) {
237 19
                unset($parent->$refKey);
238
            } else {
239 15
                unset($parent[$refKey]);
240 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...
241 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...
242
                }
243
            }
244
        }
245 32
        return $ref;
246
    }
247
}