Passed
Pull Request — master (#18)
by
unknown
04:16
created

JsonPointer   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Test Coverage

Coverage 93.28%

Importance

Changes 0
Metric Value
wmc 65
eloc 126
dl 0
loc 261
ccs 111
cts 119
cp 0.9328
rs 3.2
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getByPointer() 0 3 1
A splitPath() 0 11 3
B remove() 0 32 9
A buildPath() 0 7 3
A splitPathJsonString() 0 8 2
A splitPathURIFragment() 0 8 2
B get() 0 27 8
D add() 0 58 28
A escapeSegment() 0 6 2
A arrayGet() 0 9 3
A arrayKeyExists() 0 12 4

How to fix   Complexity   

Complex Class

Complex classes like JsonPointer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JsonPointer, and based on these observations, apply Extract Interface, too.

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 90
    public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIVE_KEY_CREATION)
98
    {
99 90
        $ref = &$holder;
100 90
        while (null !== $key = array_shift($pathItems)) {
101 82
            if ($ref instanceof \stdClass) {
102 46
                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 46
                if ($flags & self::RECURSIVE_KEY_CREATION) {
108 11
                    $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 43
                        $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 82
        if ($ref !== null && $flags & self::SKIP_IF_ISSET) {
152 1
            return;
153
        }
154 82
        $ref = $value;
155 82
    }
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 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