Fixer::silent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
/*
4
 * This file is part of the PHP-JSON-FIXER package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Json;
13
14
/**
15
 * Attempts to fix truncated JSON by padding contextual counterparts at the end.
16
 *
17
 * @author  Jitendra Adhikari <[email protected]>
18
 * @license MIT
19
 *
20
 * @link    https://github.com/adhocore/php-json-fixer
21
 */
22
class Fixer
23
{
24
    use PadsJson;
25
26
    /** @var array Current token stack indexed by position */
27
    protected $stack = [];
28
29
    /** @var bool If current char is within a string */
30
    protected $inStr = false;
31
32
    /** @var bool Whether to throw Exception on failure */
33
    protected $silent = false;
34
35
    /** @var array The complementary pairs */
36
    protected $pairs = [
37
        '{' => '}',
38
        '[' => ']',
39
        '"' => '"',
40
    ];
41
42
    /** @var int The last seen object `{` type position */
43
    protected $objectPos = -1;
44
45
    /** @var int The last seen array `[` type position */
46
    protected $arrayPos  = -1;
47
48
    /** @var string Missing value. (Options: true, false, null) */
49
    protected $missingValue = 'null';
50
51
    /**
52
     * Set/unset silent mode.
53
     *
54
     * @param bool $silent
55
     *
56
     * @return $this
57
     */
58
    public function silent($silent = true)
59
    {
60
        $this->silent = (bool) $silent;
61
62
        return $this;
63
    }
64
65
    /**
66
     * Set missing value.
67
     *
68
     * @param mixed $value
69
     *
70
     * @return $this
71
     */
72
    public function missingValue($value)
73
    {
74
        if ($value === null) {
75
            $value = 'null';
76
        } elseif (\is_bool($value)) {
77
            $value = $value ? 'true' : 'false';
78
        }
79
80
        $this->missingValue = $value;
81
82
        return $this;
83
    }
84
85
    /**
86
     * Fix the truncated JSON.
87
     *
88
     * @param string $json The JSON string to fix.
89
     *
90
     * @throws \RuntimeExcaption When fixing fails.
91
     *
92
     * @return string Fixed JSON. If failed with silent then original JSON.
93
     */
94
    public function fix($json)
95
    {
96
        list($head, $json, $tail) = $this->trim($json);
97
98
        if (empty($json) || $this->isValid($json)) {
99
            return $json;
100
        }
101
102
        if (null !== $tmpJson = $this->quickFix($json)) {
103
            return $tmpJson;
104
        }
105
106
        $this->reset();
107
108
        return $head . $this->doFix($json) . $tail;
109
    }
110
111
    protected function trim($json)
112
    {
113
        \preg_match('/^(\s*)([^\s]+)(\s*)$/', $json, $match);
114
115
        $match += ['', '', '', ''];
116
        $match[2] = \trim($json);
117
118
        \array_shift($match);
119
120
        return $match;
121
    }
122
123
    protected function isValid($json)
124
    {
125
        \json_decode($json);
126
127
        return \JSON_ERROR_NONE === \json_last_error();
128
    }
129
130
    protected function quickFix($json)
131
    {
132
        if (\strlen($json) === 1 && isset($this->pairs[$json])) {
133
            return $json . $this->pairs[$json];
134
        }
135
136
        if ($json[0] !== '"') {
137
            return $this->maybeLiteral($json);
138
        }
139
140
        return $this->padString($json);
141
    }
142
143
    protected function reset()
144
    {
145
        $this->stack     = [];
146
        $this->inStr     = false;
147
        $this->objectPos = -1;
148
        $this->arrayPos  = -1;
149
    }
150
151
    protected function maybeLiteral($json)
152
    {
153
        if (!\in_array($json[0], ['t', 'f', 'n'])) {
154
            return null;
155
        }
156
157
        foreach (['true', 'false', 'null'] as $literal) {
158
            if (\strpos($literal, $json) === 0) {
159
                return $literal;
160
            }
161
        }
162
163
        // @codeCoverageIgnoreStart
164
        return null;
165
        // @codeCoverageIgnoreEnd
166
    }
167
168
    protected function doFix($json)
169
    {
170
        list($index, $char) = [-1, ''];
171
172
        while (isset($json[++$index])) {
173
            list($prev, $char) = [$char, $json[$index]];
174
175
            $next = isset($json[$index + 1]) ? $json[$index + 1] : '';
176
177
            if (!\in_array($char, [' ', "\n", "\r"])) {
178
                $this->stack($prev, $char, $index, $next);
179
            }
180
        }
181
182
        return $this->fixOrFail($json);
183
    }
184
185
    protected function stack($prev, $char, $index, $next)
0 ignored issues
show
Unused Code introduced by
The parameter $next is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

185
    protected function stack($prev, $char, $index, /** @scrutinizer ignore-unused */ $next)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
186
    {
187
        if ($this->maybeStr($prev, $char, $index)) {
188
            return;
189
        }
190
191
        $last = $this->lastToken();
192
193
        if (\in_array($last, [',', ':', '"']) && \preg_match('/\"|\d|\{|\[|t|f|n/', $char)) {
194
            $this->popToken();
195
        }
196
197
        if (\in_array($char, [',', ':', '[', '{'])) {
198
            $this->stack[$index] = $char;
199
        }
200
201
        $this->updatePos($char, $index);
202
    }
203
204
    protected function lastToken()
205
    {
206
        return \end($this->stack);
207
    }
208
209
    protected function popToken($token = null)
210
    {
211
        // Last one
212
        if (null === $token) {
213
            return \array_pop($this->stack);
214
        }
215
216
        $keys = \array_reverse(\array_keys($this->stack));
217
        foreach ($keys as $key) {
218
            if ($this->stack[$key] === $token) {
219
                unset($this->stack[$key]);
220
                break;
221
            }
222
        }
223
    }
224
225
    protected function maybeStr($prev, $char, $index)
226
    {
227
        if ($prev !== '\\' && $char === '"') {
228
            $this->inStr = !$this->inStr;
229
        }
230
231
        if ($this->inStr && $this->lastToken() !== '"') {
232
            $this->stack[$index] = '"';
233
        }
234
235
        return $this->inStr;
236
    }
237
238
    protected function updatePos($char, $index)
239
    {
240
        if ($char === '{') {
241
            $this->objectPos = $index;
242
        } elseif ($char === '}') {
243
            $this->popToken('{');
244
            $this->objectPos = -1;
245
        } elseif ($char === '[') {
246
            $this->arrayPos = $index;
247
        } elseif ($char === ']') {
248
            $this->popToken('[');
249
            $this->arrayPos = -1;
250
        }
251
    }
252
253
    protected function fixOrFail($json)
254
    {
255
        $length  = \strlen($json);
256
        $tmpJson = $this->pad($json);
257
258
        if ($this->isValid($tmpJson)) {
259
            return $tmpJson;
260
        }
261
262
        if ($this->silent) {
263
            return $json;
264
        }
265
266
        throw new \RuntimeException(
267
            \sprintf('Could not fix JSON (tried padding `%s`)', \substr($tmpJson, $length))
268
        );
269
    }
270
}
271