Issues (41)

src/Json.php (1 issue)

Severity
1
<?php
2
3
namespace Nip\Utility;
4
5
use InvalidArgumentException;
6
use PHPUnit\Framework\Exception;
7
8
/**
9
 * Class Json
10
 * @package Nip\Utility
11
 *
12
 * @inspiration https://github.com/yiisoft/yii2/blob/ff0760142d768b03e192098d94a3c0edc2710990/framework/helpers/BaseJson.php
13
 * @inspiration https://github.com/ARCANEDEV/JSON
14
 * @inspiration https://github.com/nette/utils/blob/master/src/Utils/Json.php
15
 */
16
class Json
17
{
18
    /**
19
     * List of JSON Error messages assigned to constant names for better handling of version differences.
20
     * @var array
21
     * @since 2.0.7
22
     */
23
    public static $jsonErrorMessages = [
24
        'JSON_ERROR_DEPTH' => 'The maximum stack depth has been exceeded.',
25
        'JSON_ERROR_STATE_MISMATCH' => 'Invalid or malformed JSON.',
26
        'JSON_ERROR_CTRL_CHAR' => 'Control character error, possibly incorrectly encoded.',
27
        'JSON_ERROR_SYNTAX' => 'Syntax error.',
28
        'JSON_ERROR_UTF8' => 'Malformed UTF-8 characters, possibly incorrectly encoded.', // PHP 5.3.3
29
        'JSON_ERROR_RECURSION' => 'One or more recursive references in the value to be encoded.', // PHP 5.5.0
30
        'JSON_ERROR_INF_OR_NAN' => 'One or more NAN or INF values in the value to be encoded', // PHP 5.5.0
31
        'JSON_ERROR_UNSUPPORTED_TYPE' => 'A value of a type that cannot be encoded was given', // PHP 5.5.0
32
    ];
33
34
    /**
35
     * Encodes the given value into a JSON string.
36
     *
37
     * The method enhances `json_encode()` by supporting JavaScript expressions.
38
     * In particular, the method will not encode a JavaScript expression that is
39
     * represented in terms of a [[JsExpression]] object.
40
     *
41
     * Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
42
     * You must ensure strings passed to this method have proper encoding before passing them.
43
     *
44
     * @param mixed $value the data to be encoded.
45
     * @param int $options the encoding options. For more details please refer to
46
     * <https://secure.php.net/manual/en/function.json-encode.php>. Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`.
47
     * @return string the encoding result.
48
     * @throws InvalidArgumentException if there is any encoding error.
49
     */
50
    public static function encode($value, int $options = 320)
51
    {
52
        $expressions = [];
53
        $value = static::processData($value, $expressions, uniqid('', true));
54
        set_error_handler(function () {
55
            static::handleJsonError(JSON_ERROR_SYNTAX);
56
        }, E_WARNING);
57
        $json = json_encode($value, $options);
58
        restore_error_handler();
59
        static::handleJsonError(json_last_error());
60
61
        return $expressions === [] ? $json : strtr($json, $expressions);
62
    }
63
64
    /**
65
     * Encodes the given value into a JSON string HTML-escaping entities so it is safe to be embedded in HTML code.
66
     *
67
     * The method enhances `json_encode()` by supporting JavaScript expressions.
68
     * In particular, the method will not encode a JavaScript expression that is
69
     * represented in terms of a [[JsExpression]] object.
70
     *
71
     * Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
72
     * You must ensure strings passed to this method have proper encoding before passing them.
73
     *
74
     * @param mixed $value the data to be encoded
75
     * @return string the encoding result
76
     * @since 2.0.4
77
     * @throws InvalidArgumentException if there is any encoding error
78
     */
79
    public static function htmlEncode($value)
80
    {
81
        return static::encode($value, JSON_UNESCAPED_UNICODE | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS);
82
    }
83
84
85
    /**
86
     * Decodes the given JSON string into a PHP data structure.
87
     * @param string $json the JSON string to be decoded
88
     * @param bool $asArray whether to return objects in terms of associative arrays.
89
     * @return mixed the PHP data
90
     * @throws InvalidArgumentException if there is any decoding error
91
     */
92
    public static function decode($json, $asArray = true)
93
    {
94
        if (is_array($json)) {
0 ignored issues
show
The condition is_array($json) is always false.
Loading history...
95
            throw new InvalidArgumentException('Invalid JSON data.');
96
        } elseif ($json === null || $json === '') {
97
            return null;
98
        }
99
        $decode = json_decode((string) $json, $asArray);
100
        static::handleJsonError(json_last_error());
101
102
        return $decode;
103
    }
104
105
    /**
106
     * Handles [[encode()]] and [[decode()]] errors by throwing exceptions with the respective error message.
107
     *
108
     * @param int $lastError error code from [json_last_error()](https://secure.php.net/manual/en/function.json-last-error.php).
109
     * @throws InvalidArgumentException if there is any encoding/decoding error.
110
     * @since 2.0.6
111
     */
112
    protected static function handleJsonError($lastError)
113
    {
114
        if ($lastError === JSON_ERROR_NONE) {
115
            return;
116
        }
117
118
        $availableErrors = [];
119
        foreach (static::$jsonErrorMessages as $const => $message) {
120
            if (defined($const)) {
121
                $availableErrors[constant($const)] = $message;
122
            }
123
        }
124
125
        if (isset($availableErrors[$lastError])) {
126
            throw new InvalidArgumentException($availableErrors[$lastError], $lastError);
127
        }
128
129
        throw new InvalidArgumentException('Unknown JSON encoding/decoding error.');
130
    }
131
132
    /**
133
     * Pre-processes the data before sending it to `json_encode()`.
134
     * @param mixed $data the data to be processed
135
     * @param array $expressions collection of JavaScript expressions
136
     * @param string $expPrefix a prefix internally used to handle JS expressions
137
     * @return mixed the processed data
138
     */
139
    protected static function processData($data, &$expressions, $expPrefix)
140
    {
141
        if (is_object($data)) {
142
143
            if ($data instanceof \JsonSerializable) {
144
                return static::processData($data->jsonSerialize(), $expressions, $expPrefix);
145
            }
146
147
            if ($data instanceof \DateTimeInterface) {
148
                return static::processData((array)$data, $expressions, $expPrefix);
149
            }
150
151
            if (method_exists($data, 'toArray')) {
152
                $data = $data->toArray();
153
            } elseif ($data instanceof \SimpleXMLElement) {
154
                $data = (array) $data;
155
            } else {
156
                $result = [];
157
                foreach ($data as $name => $value) {
158
                    $result[$name] = $value;
159
                }
160
                $data = $result;
161
            }
162
163
            if ($data === []) {
164
                return new \stdClass();
165
            }
166
        }
167
168
        if (is_array($data)) {
169
            foreach ($data as $key => $value) {
170
                if (is_array($value) || is_object($value)) {
171
                    $data[$key] = static::processData($value, $expressions, $expPrefix);
172
                }
173
            }
174
        }
175
176
        return $data;
177
    }
178
    /**
179
     * Prettify json string
180
     */
181
    public static function prettify(string $json): string
182
    {
183
        $decodedJson = json_decode($json, false);
184
185
        if (json_last_error()) {
186
            throw new Exception(
187
                'Cannot prettify invalid json'
188
            );
189
        }
190
191
        return json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
192
    }
193
}