Completed
Push — master ( cc847b...44b707 )
by Viacheslav
03:28
created

JsonPointer   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Test Coverage

Coverage 93.58%

Importance

Changes 0
Metric Value
wmc 56
dl 0
loc 226
ccs 102
cts 109
cp 0.9358
rs 5.5555
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A escapeSegment() 0 6 2
A splitPath() 0 11 3
A splitPathJsonString() 0 8 2
A splitPathURIFragment() 0 8 2
D remove() 0 32 9
C get() 0 27 8
C add() 0 52 22
A getByPointer() 0 3 1
A arrayKeyExists() 0 12 4
A arrayGet() 0 9 3

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