Passed
Pull Request — master (#41)
by Sergei
02:04
created

Json::htmlEncode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Json;
6
7
use DateTimeInterface;
8
use JsonException;
9
use JsonSerializable;
10
use SimpleXMLElement;
11
use stdClass;
12
13
use function json_decode;
14
use function json_encode;
15
use function is_array;
16
use function is_object;
17
18
/**
19
 * Json is a helper class providing JSON data encoding and decoding.
20
 * It enhances the PHP built-in functions `json_encode()` and `json_decode()`
21
 * by throwing exceptions when decoding fails.
22
 */
23
final class Json
24
{
25
    /**
26
     * Encodes the given value into a JSON string.
27
     *
28
     * Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
29
     * You must ensure strings passed to this method have proper encoding before passing them.
30
     *
31
     * @param mixed $value The data to be encoded.
32
     * @param int $options The encoding options. For more details please refer to
33
     * {@see http://www.php.net/manual/en/function.json-encode.php}.
34
     * Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR`.
35
     * @param int $depth The maximum depth.
36
     *
37
     * @psalm-param int<1, 2147483647> $depth
38
     *
39
     * @throws JsonException if there is any encoding error.
40
     *
41
     * @return string The encoding result.
42
     */
43 23
    public static function encode(
44
        $value,
45
        int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR,
46
        int $depth = 512
47
    ): string {
48
        /** @psalm-var mixed $value */
49 23
        $value = self::processData($value);
50 23
        return json_encode($value, JSON_THROW_ON_ERROR | $options, $depth);
51
    }
52
53
    /**
54
     * Encodes the given value into a JSON string HTML-escaping entities so it is safe to be embedded in HTML code.
55
     *
56
     * Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
57
     * You must ensure strings passed to this method have proper encoding before passing them.
58
     *
59
     * @param mixed $value The data to be encoded.
60
     *
61
     * @throws JsonException If there is any encoding error.
62
     *
63
     * @return string The encoding result.
64
     */
65 6
    public static function htmlEncode($value): string
66
    {
67 6
        return self::encode(
68 6
            $value,
69 6
            JSON_UNESCAPED_UNICODE | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_THROW_ON_ERROR
70 6
        );
71
    }
72
73
    /**
74
     * Decodes the given JSON string into a PHP data structure.
75
     *
76
     * @param string $json The JSON string to be decoded.
77
     * @param bool $asArray Whether to return objects in terms of associative arrays.
78
     * @param int $depth The recursion depth.
79
     * @param int $options The decode options.
80
     *
81
     * @psalm-param int<1, 2147483647> $depth
82
     *
83
     * @throws JsonException If there is any decoding error.
84
     *
85
     * @return mixed The PHP data.
86
     */
87 6
    public static function decode(
88
        string $json,
89
        bool $asArray = true,
90
        int $depth = 512,
91
        int $options = JSON_THROW_ON_ERROR
92
    ) {
93 6
        if ($json === '') {
94 1
            return null;
95
        }
96 5
        return json_decode($json, $asArray, $depth, JSON_THROW_ON_ERROR | $options);
97
    }
98
99
    /**
100
     * Pre-processes the data before sending it to `json_encode()`.
101
     *
102
     * @param mixed $data The data to be processed.
103
     *
104
     * @return mixed The processed data.
105
     */
106 23
    private static function processData($data)
107
    {
108 23
        if (is_array($data)) {
109 9
            return self::processArray($data);
110
        }
111
112 19
        if (is_object($data)) {
113 15
            return self::processObject($data);
114
        }
115
116 4
        return $data;
117
    }
118
119
    /**
120
     * @param array $data
121
     * @return array
122
     */
123 10
    private static function processArray(array $data): array
124
    {
125
        /** @psalm-var mixed $value */
126 10
        foreach ($data as $key => $value) {
127 8
            if (is_array($value)) {
128 2
                $data[$key] = self::processArray($value);
129 8
            } elseif (is_object($value)) {
130
                /** @psalm-var mixed */
131 1
                $data[$key] = self::processObject($value);
132
            }
133
        }
134
135 10
        return $data;
136
    }
137
138
    /**
139
     * @param object $data
140
     * @return mixed
141
     */
142 16
    private static function processObject(object $data)
143
    {
144 16
        if ($data instanceof JsonSerializable) {
145 5
            return self::processData($data->jsonSerialize());
146
        }
147
148 13
        if ($data instanceof DateTimeInterface) {
149 2
            return $data;
150
        }
151
152 11
        if ($data instanceof SimpleXMLElement) {
153 4
            return (array)$data ?: new stdClass();
154
        }
155
156 7
        $result = [];
157
        /**
158
         * @psalm-var string $name
159
         * @psalm-var mixed $value
160
         */
161 7
        foreach ($data as $name => $value) {
162 3
            if (is_array($value)) {
163 1
                $result[$name] = self::processArray($value);
164 3
            } elseif (is_object($value)) {
165
                /** @psalm-var mixed */
166 1
                $result[$name] = self::processObject($value);
167
            } else {
168
                /** @psalm-var mixed */
169 2
                $result[$name] = $value;
170
            }
171
        }
172
173 7
        return $result ?: new stdClass();
174
    }
175
}
176