• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ducks-project / encoding-repair / 21292213003

23 Jan 2026 03:53PM UTC coverage: 96.926% (-0.4%) from 97.336%
21292213003

push

github

donaldinou
feat : remove useless tests

473 of 488 relevant lines covered (96.93%)

28.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.0
/CharsetHelper.php
1
<?php
2

3
/**
4
 * Part of EncodingRepair package.
5
 *
6
 * (c) Adrien Loyant <donald_duck@team-df.org>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace Ducks\Component\EncodingRepair;
15

16
use Ducks\Component\EncodingRepair\Detector\CallableDetector;
17
use Ducks\Component\EncodingRepair\Detector\DetectorInterface;
18
use Ducks\Component\EncodingRepair\Transcoder\CallableTranscoder;
19
use Ducks\Component\EncodingRepair\Transcoder\TranscoderInterface;
20
use InvalidArgumentException;
21
use JsonException;
22

23
/**
24
 * Static facade for charset processing.
25
 *
26
 * @psalm-api
27
 *
28
 * @final
29
 */
30
final class CharsetHelper
31
{
32
    public const AUTO = CharsetProcessorInterface::AUTO;
33
    public const WINDOWS_1252 = CharsetProcessorInterface::WINDOWS_1252;
34
    public const ENCODING_ISO = CharsetProcessorInterface::ENCODING_ISO;
35
    public const ENCODING_UTF8 = CharsetProcessorInterface::ENCODING_UTF8;
36
    public const ENCODING_UTF16 = CharsetProcessorInterface::ENCODING_UTF16;
37
    public const ENCODING_UTF32 = CharsetProcessorInterface::ENCODING_UTF32;
38
    public const ENCODING_ASCII = CharsetProcessorInterface::ENCODING_ASCII;
39

40
    /**
41
     * @var CharsetProcessorInterface|null
42
     */
43
    private static $processor = null;
44

45
    /**
46
     * @psalm-api
47
     *
48
     * @codeCoverageIgnore
49
     */
50
    private function __construct()
51
    {
52
    }
53

54
    /**
55
     * Get or initialize processor.
56
     *
57
     * @return CharsetProcessorInterface
58
     */
59
    private static function getProcessor(): CharsetProcessorInterface
48✔
60
    {
61
        if (null === self::$processor) {
48✔
62
            self::$processor = new CharsetProcessor();
48✔
63
        }
64

65
        return self::$processor;
48✔
66
    }
67

68
    /**
69
     * Register a transcoder with optional priority.
70
     *
71
     * @phpcs:disable Generic.Files.LineLength.TooLong
72
     *
73
     * @param TranscoderInterface|callable(string, string, string, null|array<string, mixed>): (string|null) $transcoder Transcoder instance or callable
74
     * @param int|null $priority Priority override (null = use transcoder's default)
75
     *
76
     * @return void
77
     *
78
     * @throws InvalidArgumentException If transcoder is invalid
79
     *
80
     * @phpcs:enable Generic.Files.LineLength.TooLong
81
     */
82
    public static function registerTranscoder($transcoder, ?int $priority = null): void
4✔
83
    {
84
        /** @var mixed $transcoder */
85
        if ($transcoder instanceof TranscoderInterface) {
4✔
86
            // @codeCoverageIgnoreStart
87
            self::getProcessor()->registerTranscoder($transcoder, $priority);
88
            return;
89
            // @codeCoverageIgnoreEnd
90
        }
91

92
        if (\is_callable($transcoder)) {
3✔
93
            /** @var callable(string, string, string, null|array<string, mixed>): (string|null) $transcoder */
94
            $wrapper = new CallableTranscoder($transcoder, $priority ?? 0);
2✔
95
            self::getProcessor()->registerTranscoder($wrapper, $priority);
2✔
96
            return;
2✔
97
        }
98

99
        throw new InvalidArgumentException(
1✔
100
            'Transcoder must be an instance of TranscoderInterface or a callable'
1✔
101
        );
1✔
102
    }
103

104
    /**
105
     * Register a detector with optional priority.
106
     *
107
     * @phpcs:disable Generic.Files.LineLength.TooLong
108
     *
109
     * @param DetectorInterface|callable(string, array<string, mixed>|null): (string|null) $detector Detector instance or callable
110
     * @param int|null $priority Priority override (null = use detector's default)
111
     *
112
     * @return void
113
     *
114
     * @throws InvalidArgumentException If detector is invalid
115
     *
116
     * @phpcs:enable Generic.Files.LineLength.TooLong
117
     */
118
    public static function registerDetector($detector, ?int $priority = null): void
4✔
119
    {
120
        /** @var mixed $detector */
121
        if ($detector instanceof DetectorInterface) {
4✔
122
            // @codeCoverageIgnoreStart
123
            self::getProcessor()->registerDetector($detector, $priority);
124
            return;
125
            // @codeCoverageIgnoreEnd
126
        }
127

128
        if (\is_callable($detector)) {
3✔
129
            /** @var callable(string, array<string, mixed>|null): (string|null) $detector */
130
            $wrapper = new CallableDetector($detector, $priority ?? 0);
2✔
131
            self::getProcessor()->registerDetector($wrapper, $priority);
2✔
132
            return;
2✔
133
        }
134

135
        throw new InvalidArgumentException(
1✔
136
            'Detector must be an instance of DetectorInterface or a callable'
1✔
137
        );
1✔
138
    }
139

140
    /**
141
     * Detects the charset encoding of a string.
142
     *
143
     * @param string $string String to analyze
144
     * @param array<string, mixed> $options Conversion options
145
     *                                      - 'encodings': array of encodings to test
146
     *
147
     * @return string Detected encoding (uppercase)
148
     */
149
    public static function detect(string $string, array $options = []): string
7✔
150
    {
151
        return self::getProcessor()->detect($string, $options);
7✔
152
    }
153

154
    /**
155
     * Batch detects the charset encoding of iterable items.
156
     *
157
     * @param iterable<mixed> $items items to loop for analyzis
158
     * @param array<string, mixed> $options Conversion options
159
     *                                      - 'encodings': array of encodings to test
160
     *                                      - 'maxSamples': int number of samples to test (default: 1)
161
     *
162
     * @return string Detected encoding (uppercase)
163
     */
164
    public static function detectBatch(iterable $items, array $options = []): string
×
165
    {
166
        return self::getProcessor()->detectBatch($items, $options);
×
167
    }
168

169
    /**
170
     * Convert $data string from one encoding to another.
171
     *
172
     * @param mixed $data Data to convert
173
     * @param string $to Target encoding
174
     * @param string $from Source encoding (use AUTO for detection)
175
     * @param array<string, mixed> $options Conversion options
176
     *                                      - 'normalize': bool (default: true)
177
     *                                      - 'translit': bool (default: true)
178
     *                                      - 'ignore': bool (default: true)
179
     *
180
     * @return mixed The data transcoded in the target encoding
181
     *
182
     * @throws InvalidArgumentException If encoding is invalid
183
     */
184
    public static function toCharset(
10✔
185
        $data,
186
        string $to = self::ENCODING_UTF8,
187
        string $from = self::ENCODING_ISO,
188
        array $options = []
189
    ) {
190
        return self::getProcessor()->toCharset($data, $to, $from, $options);
10✔
191
    }
192

193
    /**
194
     * Batch convert array items from one encoding to another.
195
     *
196
     * @param array<mixed> $items Items to convert
197
     * @param string $to Target encoding
198
     * @param string $from Source encoding (use AUTO for detection)
199
     * @param array<string, mixed> $options Conversion options
200
     *
201
     * @return array<mixed> Converted items
202
     *
203
     * @throws InvalidArgumentException If encoding is invalid
204
     */
205
    public static function toCharsetBatch(
3✔
206
        array $items,
207
        string $to = self::ENCODING_UTF8,
208
        string $from = self::ENCODING_ISO,
209
        array $options = []
210
    ): array {
211
        return self::getProcessor()->toCharsetBatch($items, $to, $from, $options);
3✔
212
    }
213

214
    /**
215
     * Converts anything (string, array, object) to UTF-8.
216
     *
217
     * @param mixed $data Data to convert
218
     * @param string $from Source encoding
219
     * @param array<string, mixed> $options Conversion options
220
     *                                      - 'normalize': bool (default: true)
221
     *                                      - 'translit': bool (default: true)
222
     *                                      - 'ignore': bool (default: true)
223
     *
224
     * @return mixed
225
     *
226
     * @throws InvalidArgumentException If encoding is invalid
227
     */
228
    public static function toUtf8($data, string $from = self::WINDOWS_1252, array $options = [])
10✔
229
    {
230
        return self::getProcessor()->toCharset($data, self::ENCODING_UTF8, $from, $options);
10✔
231
    }
232

233
    /**
234
     * Converts anything to ISO-8859-1 (Windows-1252).
235
     *
236
     * @param mixed $data Data to convert
237
     * @param string $from Source encoding
238
     * @param array<string, mixed> $options Conversion options
239
     *                                      - 'normalize': bool (default: true)
240
     *                                      - 'translit': bool (default: true)
241
     *                                      - 'ignore': bool (default: true)
242
     *
243
     * @return mixed
244
     *
245
     * @throws InvalidArgumentException If encoding is invalid
246
     */
247
    public static function toIso($data, string $from = self::ENCODING_UTF8, array $options = [])
1✔
248
    {
249
        return self::getProcessor()->toCharset($data, self::WINDOWS_1252, $from, $options);
1✔
250
    }
251

252
    /**
253
     * Repairs double-encoded strings.
254
     *
255
     * Attempts to fix strings that have been encoded multiple times
256
     * by detecting and reversing the encoding layers.
257
     * Pay attention that it will first repair within UTF-8, then converts to $to.
258
     *
259
     * @param mixed $data Data to repair
260
     * @param string $to Target encoding (UTF-8, ISO, etc.)
261
     * @param string $from The "glitch" encoding (usually ISO/Windows-1252) that caused the double encoding.
262
     * @param array<string,mixed> $options Conversion options
263
     *                                     - 'normalize': bool (default: true)
264
     *                                     - 'translit': bool (default: true)
265
     *                                     - 'ignore': bool (default: true)
266
     *                                     - 'maxDepth' : int (default: 5)
267
     *
268
     * @return mixed
269
     *
270
     * @throws InvalidArgumentException If encoding is invalid
271
     */
272
    public static function repair(
7✔
273
        $data,
274
        string $to = self::ENCODING_UTF8,
275
        string $from = self::ENCODING_ISO,
276
        array $options = []
277
    ) {
278
        return self::getProcessor()->repair($data, $to, $from, $options);
7✔
279
    }
280

281
    /**
282
     * Safe JSON encoding to ensure UTF-8 compliance.
283
     *
284
     * Note: JSON_THROW_ON_ERROR flag is automatically added to $flags.
285
     *
286
     * @param mixed $data
287
     * @param int $flags JSON encode flags (JSON_THROW_ON_ERROR is automatically added)
288
     * @param int<1, 2147483647> $depth Maximum depth
289
     * @param string $from Source encoding for repair
290
     *
291
     * @return string JSON UTF-8 string
292
     *
293
     * @throws JsonException If JSON encoding fails
294
     */
295
    public static function safeJsonEncode(
5✔
296
        $data,
297
        int $flags = 0,
298
        int $depth = 512,
299
        string $from = self::WINDOWS_1252
300
    ): string {
301
        return self::getProcessor()->safeJsonEncode($data, $flags, $depth, $from);
5✔
302
    }
303

304
    /**
305
     * Safe JSON decoding with charset conversion.
306
     *
307
     * Note: JSON_THROW_ON_ERROR flag is automatically added to $flags.
308
     *
309
     * @param string $json JSON string
310
     * @param bool|null $associative Return associative array
311
     * @param int<1, 2147483647> $depth Maximum depth
312
     * @param int $flags JSON decode flags (JSON_THROW_ON_ERROR is automatically added)
313
     * @param string $to Target encoding
314
     * @param string $from Source encoding for repair
315
     *
316
     * @return mixed Decoded data
317
     *
318
     * @throws JsonException If JSON decoding fails
319
     */
320
    public static function safeJsonDecode(
5✔
321
        string $json,
322
        ?bool $associative = null,
323
        int $depth = 512,
324
        int $flags = 0,
325
        string $to = self::ENCODING_UTF8,
326
        string $from = self::WINDOWS_1252
327
    ) {
328
        return self::getProcessor()->safeJsonDecode($json, $associative, $depth, $flags, $to, $from);
5✔
329
    }
330
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc