Passed
Branch master (d01bfd)
by Delete
02:01
created

Converter::convertEncoding()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 10
c 3
b 0
f 0
nc 5
nop 3
dl 0
loc 17
rs 8.8571
1
<?php
2
namespace Crossjoin\Json;
3
4
use Crossjoin\Json\Exception\ConversionFailedException;
5
use Crossjoin\Json\Exception\ExtensionRequiredException;
6
use Crossjoin\Json\Exception\InvalidArgumentException;
7
use Crossjoin\Json\Exception\NativeJsonErrorException;
8
9
/**
10
 * Class Converter
11
 *
12
 * @package Crossjoin\Json
13
 * @author Christoph Ziegenberg <[email protected]>
14
 */
15
abstract class Converter
16
{
17
    const UTF8    = 'UTF-8';
18
    const UTF16BE = 'UTF-16BE';
19
    const UTF16LE = 'UTF-16LE';
20
    const UTF32BE = 'UTF-32BE';
21
    const UTF32LE = 'UTF-32LE';
22
23
    /**
24
     * @param string $string
25
     * @param string $fromEncoding
26
     * @param string $toEncoding
27
     *
28
     * @return string
29
     * @throws \Crossjoin\Json\Exception\InvalidArgumentException
30
     * @throws \Crossjoin\Json\Exception\ConversionFailedException
31
     * @throws \Crossjoin\Json\Exception\ExtensionRequiredException
32
     */
33
    public function convertEncoding($string, $fromEncoding, $toEncoding)
34
    {
35
        // Check arguments
36
        if (!is_string($string)) {
37
            throw InvalidArgumentException::getInstance('string', 'json', $string, 1478195990);
38
        } elseif (!is_string($fromEncoding)) {
39
            throw InvalidArgumentException::getInstance('string', 'fromEncoding', $fromEncoding, 1478195991);
40
        } elseif (!is_string($toEncoding)) {
41
            throw InvalidArgumentException::getInstance('string', 'toEncoding', $toEncoding, 1478195992);
42
        }
43
44
        if ($fromEncoding === $toEncoding) {
45
            return $string;
46
        }
47
48
        return $this->tryConvertEncoding($string, $fromEncoding, $toEncoding);
49
    }
50
51
    /**
52
     * @param $string
53
     * @param $fromEncoding
54
     * @param $toEncoding
55
     *
56
     * @return string
57
     * @throws \Crossjoin\Json\Exception\ExtensionRequiredException
58
     * @throws \Crossjoin\Json\Exception\ConversionFailedException
59
     */
60
    private function tryConvertEncoding($string, $fromEncoding, $toEncoding)
61
    {
62
        // Try different conversion functions, ordered by speed
63
        if (($converted = $this->convertWithIconv($string, $fromEncoding, $toEncoding)) !== null) {
64
            return $converted;
65
        } elseif (($converted = $this->convertWithUConverter($string, $fromEncoding, $toEncoding)) !== null) {
66
            return $converted;
67
        } elseif (($converted = $this->convertWithMultiByteString($string, $fromEncoding, $toEncoding)) !== null) {
68
            return $converted;
69
        }
70
71
        // No available method found
72
        throw new ExtensionRequiredException(
73
            "The 'iconv', 'intl' or the 'mbstring' extension is required to convert the JSON encoding.",
74
            1478095252
75
        );
76
    }
77
78
    /**
79
     * Removes the byte order mark (BOM) from the JSON text. This is not allowed in JSON,
80
     * but may be ignored when parsing it.
81
     *
82
     * @param string $string
83
     *
84
     * @return string
85
     * @throws \Crossjoin\Json\Exception\InvalidArgumentException
86
     */
87
    public function removeByteOrderMark($string)
88
    {
89
        // Check arguments
90
        if (!is_string($string)) {
91
            throw InvalidArgumentException::getInstance('string', 'string', $string, 1478195910);
92
        }
93
94
        return (string)preg_replace(
95
            '/^(?:' .
96
            '\xEF\xBB\xBF|' .     // UTF-8 BOM
97
            '\x00\x00\xFE\xFF|' . // UTF-32BE BOM
98
            '\xFF\xFE\x00\x00|' . // UTF-32LE BOM (before UTF-16LE check!)
99
            '\xFE\xFF|' .         // UTF-16BE BOM
100
            '\xFF\xFE' .          // UTF-16LE BOM
101
            ')/',
102
            '',
103
            $string
104
        );
105
    }
106
107
    /**
108
     * @return NativeJsonErrorException
109
     */
110
    protected function getNativeJsonErrorException()
111
    {
112
        if (function_exists('\json_last_error_msg')) {
113
            return new NativeJsonErrorException(\json_last_error_msg(), \json_last_error());
114
        } else {
115
            return new NativeJsonErrorException('An error occurred while encoding/decoding JSON.', \json_last_error());
116
        }
117
    }
118
119
    /**
120
     * @param $string
121
     * @param $fromEncoding
122
     * @param $toEncoding
123
     *
124
     * @return string|null
125
     * @throws \Crossjoin\Json\Exception\ConversionFailedException
126
     */
127
    private function convertWithIconv($string, $fromEncoding, $toEncoding)
128
    {
129
        // @codeCoverageIgnoreStart
130
        if (function_exists('iconv')) {
131
            $string = iconv($fromEncoding, $toEncoding . '//IGNORE', $string);
132
            if ($string === false) {
133
                throw new ConversionFailedException('Error while converting the encoding.', 1478193725);
134
            }
135
            return $string;
136
        }
137
        return null;
138
        // @codeCoverageIgnoreEnd
139
    }
140
141
    /**
142
     * @param $string
143
     * @param $fromEncoding
144
     * @param $toEncoding
145
     *
146
     * @return string|null
147
     */
148
    private function convertWithUConverter($string, $fromEncoding, $toEncoding)
149
    {
150
        // @codeCoverageIgnoreStart
151
        if (class_exists('\\UConverter')) {
152
            /** @noinspection PhpUndefinedClassInspection */
153
            $uConverter = new \UConverter($toEncoding, $fromEncoding);
154
            /** @noinspection PhpUndefinedMethodInspection */
155
            return $uConverter->convert($string);
156
        }
157
        return null;
158
        // @codeCoverageIgnoreEnd
159
    }
160
161
    /**
162
     * @param $string
163
     * @param $fromEncoding
164
     * @param $toEncoding
165
     *
166
     * @return string|null
167
     */
168
    private function convertWithMultiByteString($string, $fromEncoding, $toEncoding)
169
    {
170
        // @codeCoverageIgnoreStart
171
        if (function_exists('mb_convert_encoding')) {
172
            return mb_convert_encoding($string, $toEncoding, $fromEncoding);
173
        }
174
        return null;
175
        // @codeCoverageIgnoreEnd
176
    }
177
}
178