fix: normalize order response to valid UTF-8 before JSON encoding (#83)
* fix: normalize cart json utf8 and add tests Ensure cart and checkout responses are cleaned to valid UTF-8 before JSON encoding and cover Utf8Cleaner with unit tests. Made-with: Cursor * fix: normalize order response to valid UTF-8 before JSON encoding - Add Utf8Cleaner::clean() for successful order creation response (data) - Add Utf8Cleaner::clean() for validation error response (ErrorBag) - Prevents Malformed UTF-8 characters error when creating order with non-UTF-8 data from OpenCart/DB (same approach as cart endpoints) Made-with: Cursor
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Handlers;
|
||||
|
||||
use App\Services\CartService;
|
||||
use App\Support\Utf8Cleaner;
|
||||
use Cart\Cart;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Openguru\OpenCartFramework\Http\Request;
|
||||
@@ -23,7 +24,7 @@ class CartHandler
|
||||
$items = $this->cartService->getCart();
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $items,
|
||||
'data' => Utf8Cleaner::clean($items),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ class CartHandler
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $items,
|
||||
'data' => Utf8Cleaner::clean($items),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Handlers;
|
||||
|
||||
use App\Exceptions\OrderValidationFailedException;
|
||||
use App\Services\OrderCreateService;
|
||||
use App\Support\Utf8Cleaner;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Openguru\OpenCartFramework\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -26,11 +27,11 @@ class OrderHandler
|
||||
]);
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $order,
|
||||
'data' => Utf8Cleaner::clean($order),
|
||||
], Response::HTTP_CREATED);
|
||||
} catch (OrderValidationFailedException $exception) {
|
||||
return new JsonResponse([
|
||||
'data' => $exception->getErrorBag()->firstOfAll(),
|
||||
'data' => Utf8Cleaner::clean($exception->getErrorBag()->firstOfAll()),
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
class Utf8Cleaner
|
||||
{
|
||||
/**
|
||||
* Рекурсивно проходит по массиву/объекту и приводит все строковые значения к валидному UTF-8.
|
||||
*/
|
||||
public static function clean($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $item) {
|
||||
$value[$key] = self::clean($item);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_object($value)) {
|
||||
foreach (get_object_vars($value) as $property => $item) {
|
||||
$value->{$property} = self::clean($item);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
// Попробовать нормализовать строку в UTF-8, подменяя невалидные последовательности.
|
||||
$converted = @mb_convert_encoding($value, 'UTF-8', 'UTF-8');
|
||||
|
||||
if ($converted === false) {
|
||||
// Фолбэк: заменить невалидные байты стандартным алгоритмом json_encode.
|
||||
$encoded = json_encode($value, JSON_INVALID_UTF8_SUBSTITUTE);
|
||||
|
||||
if ($encoded === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Убираем кавычки, которые добавляет json_encode.
|
||||
return json_decode($encoded, true) ?? '';
|
||||
}
|
||||
|
||||
return $converted;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Support;
|
||||
|
||||
use App\Support\Utf8Cleaner;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class Utf8CleanerTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function returns_non_string_values_as_is(): void
|
||||
{
|
||||
$this->assertSame(123, Utf8Cleaner::clean(123));
|
||||
$this->assertSame(12.3, Utf8Cleaner::clean(12.3));
|
||||
$this->assertSame(true, Utf8Cleaner::clean(true));
|
||||
$this->assertSame(false, Utf8Cleaner::clean(false));
|
||||
$this->assertNull(Utf8Cleaner::clean(null));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function leaves_valid_utf8_strings_unchanged(): void
|
||||
{
|
||||
$value = 'Привет, мир! 😀';
|
||||
|
||||
$this->assertSame($value, Utf8Cleaner::clean($value));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cleans_strings_with_invalid_utf8_bytes(): void
|
||||
{
|
||||
// Строка с заведомо некорректной UTF-8 последовательностью.
|
||||
$invalid = "\xF0\x28\x8C\x28";
|
||||
|
||||
$cleaned = Utf8Cleaner::clean($invalid);
|
||||
|
||||
$this->assertIsString($cleaned);
|
||||
$this->assertNotSame($invalid, $cleaned);
|
||||
|
||||
$json = json_encode($cleaned);
|
||||
$this->assertNotFalse($json, 'Resulting string must be valid UTF-8 for json_encode');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cleans_nested_arrays_recursively(): void
|
||||
{
|
||||
$invalid = "\xF0\x28\x8C\x28";
|
||||
|
||||
$data = [
|
||||
'foo' => 'bar',
|
||||
'nested' => [
|
||||
'value' => $invalid,
|
||||
],
|
||||
];
|
||||
|
||||
$cleaned = Utf8Cleaner::clean($data);
|
||||
|
||||
$this->assertIsArray($cleaned);
|
||||
$this->assertSame('bar', $cleaned['foo']);
|
||||
$this->assertIsString($cleaned['nested']['value']);
|
||||
$this->assertNotSame($invalid, $cleaned['nested']['value']);
|
||||
|
||||
$json = json_encode($cleaned);
|
||||
$this->assertNotFalse($json);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cleans_objects_recursively(): void
|
||||
{
|
||||
$invalid = "\xF0\x28\x8C\x28";
|
||||
|
||||
$object = new class {
|
||||
public string $name = 'test';
|
||||
public array $meta = [];
|
||||
};
|
||||
|
||||
$object->meta['invalid'] = $invalid;
|
||||
|
||||
$cleaned = Utf8Cleaner::clean($object);
|
||||
|
||||
$this->assertSame('test', $cleaned->name);
|
||||
$this->assertIsString($cleaned->meta['invalid']);
|
||||
$this->assertNotSame($invalid, $cleaned->meta['invalid']);
|
||||
|
||||
$json = json_encode($cleaned);
|
||||
$this->assertNotFalse($json);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user