diff --git a/lib/private/Memcache/KeyValueCache.php b/lib/private/Memcache/KeyValueCache.php index 9125290d801f4..c3b1083e5d3d2 100644 --- a/lib/private/Memcache/KeyValueCache.php +++ b/lib/private/Memcache/KeyValueCache.php @@ -242,13 +242,13 @@ private function normalizeTtl(int $ttl): int { * Serialize a value for storage in the key-value store. */ protected static function encodeValue(mixed $value): string { - return is_int($value) ? (string)$value : json_encode($value, JSON_THROW_ON_ERROR); + return is_int($value) ? (string)$value : json_encode($value, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION); } /** * Unserialize a value from the key-value store. */ protected static function decodeValue(string $value): mixed { - return is_numeric($value) ? (int)$value : json_decode($value, true); + return json_decode($value, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index afd813921e7be..6d41f517a66ab 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -233,10 +233,10 @@ protected function evalLua(string $scriptName, array $keys, array $args) { } protected static function encodeValue(mixed $value): string { - return is_int($value) ? (string)$value : json_encode($value); + return is_int($value) ? (string)$value : json_encode($value, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION); } protected static function decodeValue(string $value): mixed { - return is_numeric($value) ? (int)$value : json_decode($value, true); + return json_decode($value, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/tests/lib/Memcache/Cache.php b/tests/lib/Memcache/Cache.php index a4268a36c5371..dce755793fab5 100644 --- a/tests/lib/Memcache/Cache.php +++ b/tests/lib/Memcache/Cache.php @@ -16,6 +16,40 @@ abstract class Cache extends \Test\Cache\TestCache { */ protected $instance; + /** + * @return array + */ + public static function roundtripValuesProvider(): array { + return [ + 'string' => ['some string value'], + 'empty string' => [''], + 'numeric string' => ['0123'], + 'float-like string' => ['3.14'], + 'scientific notation string' => ['1e3'], + 'integer' => [1234], + 'zero' => [0], + 'negative integer' => [-42], + 'float' => [3.14], + 'zero-fraction float' => [1.0], + 'negative float' => [-2.75], + 'scientific notation float' => [1.2e3], + 'boolean true' => [true], + 'boolean false' => [false], + 'null' => [null], + 'list' => [['a', 'b', 'c']], + 'associative array' => [['foo' => 'bar', 'baz' => 42]], + 'mixed nested array' => [[ + 'int' => 7, + 'float' => 3.14, + 'whole-float' => 1.0, + 'string' => '3.14', + 'bool' => true, + 'null' => null, + 'list' => [1, 2.5, '3'], + ]], + ]; + } + public function testExistsAfterSet(): void { $this->assertFalse($this->instance->hasKey('foo')); $this->instance->set('foo', 'bar'); @@ -34,6 +68,15 @@ public function testGetArrayAfterSet(): void { $this->assertEquals(['bar'], $this->instance->get('foo')); } + #[\PHPUnit\Framework\Attributes\DataProvider('roundtripValuesProvider')] + public function testStoreAndReadBack(mixed $value): void { + $this->assertNull($this->instance->get('roundtrip'), 'key should be empty before storing'); + + $this->assertNotFalse($this->instance->set('roundtrip', $value), 'value should be stored'); + $this->assertTrue($this->instance->hasKey('roundtrip'), 'stored key should exist'); + $this->assertSame($value, $this->instance->get('roundtrip'), 'stored value should be read back unchanged'); + } + public function testDoesNotExistAfterRemove(): void { $this->instance->set('foo', 'bar'); $this->instance->remove('foo');