diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index 5cb123b..b9aa64f 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -12,24 +12,25 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- php-version: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
+ php-version: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
name: PHP ${{ matrix.php-version }}
steps:
- uses: actions/checkout@v6
+
+ - name: Set up PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: mbstring
+
- name: Validate composer.json and composer.lock
run: composer validate
- - name: Cache Composer packages
- id: composer-cache
- uses: actions/cache@v5
- with:
- path: vendor
- key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
- restore-keys: |
- ${{ runner.os }}-php-
+
- name: Install suggested dependencies
- run: composer require jeremeamia/superclosure opis/closure
- - name: Install dependencies
- if: steps.composer-cache.outputs.cache-hit != 'true'
- run: composer install --prefer-dist --no-progress --no-suggest
+ run: composer require jeremeamia/superclosure opis/closure --no-update --no-interaction --ansi
+
+ - name: Install Composer dependencies
+ uses: ramsey/composer-install@v4
+
- name: Run test suite
run: vendor/bin/phpunit
diff --git a/phpunit.xml b/phpunit.xml
index 8e5ecc0..3c6fc88 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -9,7 +9,9 @@
colors="true">
- ./tests
+ ./tests/EnumSerializationTest.php
+ ./tests/JsonSerializerTest.php
+ ./tests/ClosureSerializer
diff --git a/src/JsonSerializer/JsonSerializer.php b/src/JsonSerializer/JsonSerializer.php
index 4b16ea2..231d268 100644
--- a/src/JsonSerializer/JsonSerializer.php
+++ b/src/JsonSerializer/JsonSerializer.php
@@ -308,10 +308,10 @@ protected function serializeData($value)
*/
protected function serializeObject($value)
{
- if ($this->objectStorage->contains($value)) {
+ if ($this->objectStorage->offsetExists($value)) {
return [static::CLASS_IDENTIFIER_KEY => '@' . $this->objectStorage[$value]];
}
- $this->objectStorage->attach($value, $this->objectMappingIndex++);
+ $this->objectStorage->offsetSet($value, $this->objectMappingIndex++);
$ref = new ReflectionClass($value);
$className = $ref->getName();
@@ -402,8 +402,10 @@ protected function extractObjectData($value, $ref, $properties)
foreach ($properties as $property) {
try {
$propRef = $this->getReflectionProperty($ref, $property);
- $propRef->setAccessible(true);
- if (!$propRef->isInitialized($value)) {
+ if (PHP_VERSION_ID < 80100) {
+ $propRef->setAccessible(true);
+ }
+ if (PHP_VERSION_ID >= 70400 && !$propRef->isInitialized($value)) {
continue;
}
$data[$property] = $propRef->getValue($value);
@@ -548,7 +550,9 @@ protected function unserializeObject($value)
foreach ($value as $property => $propertyValue) {
try {
$propRef = $this->getReflectionProperty($ref, $property);
- $propRef->setAccessible(true);
+ if (PHP_VERSION_ID < 80100) {
+ $propRef->setAccessible(true);
+ }
$propRef->setValue($obj, $this->unserializeData($propertyValue));
} catch (ReflectionException $e) {
switch ($this->undefinedAttributeMode) {
diff --git a/tests/ClosureSerializer/ClosureSerializerManagerTest.php b/tests/ClosureSerializer/ClosureSerializerManagerTest.php
index b52abf4..f31fc2d 100644
--- a/tests/ClosureSerializer/ClosureSerializerManagerTest.php
+++ b/tests/ClosureSerializer/ClosureSerializerManagerTest.php
@@ -10,7 +10,7 @@ class ClosureSerializerManagerTest extends TestCase
{
public function setUp(): void
{
- if (! class_exists(\SuperClosure\SerializerInterface::class)) {
+ if (! interface_exists(\SuperClosure\SerializerInterface::class)) {
$this->markTestSkipped('Missing jeremeamia/superclosure to run this test');
}
}
diff --git a/tests/ClosureSerializer/SuperClosureSerializerTest.php b/tests/ClosureSerializer/SuperClosureSerializerTest.php
index 092220e..0684738 100644
--- a/tests/ClosureSerializer/SuperClosureSerializerTest.php
+++ b/tests/ClosureSerializer/SuperClosureSerializerTest.php
@@ -9,7 +9,7 @@ class SuperClosureSerializerTest extends TestCase
{
public function setUp(): void
{
- if (! class_exists(\SuperClosure\SerializerInterface::class)) {
+ if (! interface_exists(\SuperClosure\SerializerInterface::class)) {
$this->markTestSkipped('Missing jeremeamia/superclosure to run this test');
}
}
diff --git a/tests/EnumSerializationTest.php b/tests/EnumSerializationTest.php
new file mode 100644
index 0000000..f456c82
--- /dev/null
+++ b/tests/EnumSerializationTest.php
@@ -0,0 +1,145 @@
+serializer = new JsonSerializer(null, $customObjectSerializerMap);
+ }
+
+ /**
+ * Test serialization of Enums
+ *
+ * @return void
+ */
+ public function testSerializeEnums()
+ {
+ $unitEnum = SupportEnums\MyUnitEnum::Hearts;
+ $expected = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"}';
+ $this->assertSame($expected, $this->serializer->serialize($unitEnum));
+
+ $backedEnum = SupportEnums\MyBackedEnum::Hearts;
+ $expected = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"}';
+ $this->assertSame($expected, $this->serializer->serialize($backedEnum));
+
+ $intBackedEnum = SupportEnums\MyIntBackedEnum::One;
+ $expected = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"One","value":1}';
+ $this->assertSame($expected, $this->serializer->serialize($intBackedEnum));
+ }
+
+ /**
+ * Test serialization of multiple Enums
+ *
+ * @return void
+ */
+ public function testSerializeMultipleEnums()
+ {
+ $obj = new stdClass();
+ $obj->enum1 = SupportEnums\MyUnitEnum::Hearts;
+ $obj->enum2 = SupportEnums\MyBackedEnum::Hearts;
+ $obj->enum3 = SupportEnums\MyIntBackedEnum::One;
+ $obj->enum4 = SupportEnums\MyUnitEnum::Hearts;
+ $obj->enum5 = SupportEnums\MyBackedEnum::Hearts;
+ $obj->enum6 = SupportEnums\MyIntBackedEnum::One;
+
+ $expected = '{"@type":"stdClass","enum1":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"},"enum2":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"},"enum3":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"One","value":1},"enum4":{"@type":"@1"},"enum5":{"@type":"@2"},"enum6":{"@type":"@3"}}';
+ $this->assertSame($expected, $this->serializer->serialize($obj));
+ }
+
+ /**
+ * Test unserialization of Enums
+ *
+ * @return void
+ */
+ public function testUnserializeEnums()
+ {
+ $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"}';
+ $obj = $this->serializer->unserialize($serialized);
+ $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyUnitEnum', $obj);
+ $this->assertSame(SupportEnums\MyUnitEnum::Hearts, $obj);
+
+ $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"}';
+ $obj = $this->serializer->unserialize($serialized);
+ $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyBackedEnum', $obj);
+ $this->assertSame(SupportEnums\MyBackedEnum::Hearts, $obj);
+
+ $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"Two","value":2}';
+ $obj = $this->serializer->unserialize($serialized);
+ $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyIntBackedEnum', $obj);
+ $this->assertSame(SupportEnums\MyIntBackedEnum::Two, $obj);
+ $this->assertSame(SupportEnums\MyIntBackedEnum::Two->value, $obj->value);
+
+ // wrong value of BackedEnum is ignored
+ $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"S"}';
+ $obj = $this->serializer->unserialize($serialized);
+ $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyBackedEnum', $obj);
+ $this->assertSame(SupportEnums\MyBackedEnum::Hearts, $obj);
+ $this->assertSame(SupportEnums\MyBackedEnum::Hearts->value, $obj->value);
+ }
+
+ /**
+ * Test unserialization of multiple Enums
+ *
+ * @return void
+ */
+ public function testUnserializeMultipleEnums()
+ {
+ $obj = new stdClass();
+ $obj->enum1 = SupportEnums\MyUnitEnum::Hearts;
+ $obj->enum2 = SupportEnums\MyBackedEnum::Hearts;
+ $obj->enum3 = SupportEnums\MyIntBackedEnum::One;
+ $obj->enum4 = SupportEnums\MyUnitEnum::Hearts;
+ $obj->enum5 = SupportEnums\MyBackedEnum::Hearts;
+ $obj->enum6 = SupportEnums\MyIntBackedEnum::One;
+
+ $serialized = '{"@type":"stdClass","enum1":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"},"enum2":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"},"enum3":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"One","value":1},"enum4":{"@type":"@1"},"enum5":{"@type":"@2"},"enum6":{"@type":"@3"}}';
+ $actualObj = $this->serializer->unserialize($serialized);
+ $this->assertInstanceOf('stdClass', $actualObj);
+ $this->assertEquals($obj, $actualObj);
+ }
+
+ /**
+ * Test unserialization of wrong UnitEnum
+ *
+ * @return void
+ */
+ public function testUnserializeWrongUnitEnum() {
+ // bad case generate Error
+ $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Circles"}';
+ $this->expectException(\Error::class);
+ $this->serializer->unserialize($serialized);
+ }
+
+ /**
+ * Test unserialization of wrong BackedEnum
+ *
+ * @return void
+ */
+ public function testUnserializeWrongBackedEnum() {
+ // bad case generate Error
+ $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Circles","value":"C"}';
+ $this->expectException(\Error::class);
+ $this->serializer->unserialize($serialized);
+ }
+}
diff --git a/tests/JsonSerializerTest.php b/tests/JsonSerializerTest.php
index f6bdb55..ef23482 100644
--- a/tests/JsonSerializerTest.php
+++ b/tests/JsonSerializerTest.php
@@ -230,10 +230,14 @@ public function testUnserializeObjects()
$this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportClasses\AllVisibilities', $obj);
$this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportClasses\EmptyClass', $obj->pub);
$prop = new ReflectionProperty($obj, 'prot');
- $prop->setAccessible(true);
+ if (PHP_VERSION_ID < 80100) {
+ $prop->setAccessible(true);
+ }
$this->assertSame('protected', $prop->getValue($obj));
$prop = new ReflectionProperty($obj, 'priv');
- $prop->setAccessible(true);
+ if (PHP_VERSION_ID < 80100) {
+ $prop->setAccessible(true);
+ }
$this->assertSame('dont tell anyone', $prop->getValue($obj));
$serialized = '{"instance":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportClasses\\\\EmptyClass"}}';
@@ -265,146 +269,6 @@ public function testSerializeObjectWithParentPrivateProperty()
$this->assertSame('parentPublicValue', $restored->parentPublic);
}
-
- /**
- * Test serialization of Enums
- *
- * @return void
- */
- public function testSerializeEnums()
- {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped("Enums are only available since PHP 8.1");
- }
-
- $unitEnum = SupportEnums\MyUnitEnum::Hearts;
- $expected = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"}';
- $this->assertSame($expected, $this->serializer->serialize($unitEnum));
-
- $backedEnum = SupportEnums\MyBackedEnum::Hearts;
- $expected = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"}';
- $this->assertSame($expected, $this->serializer->serialize($backedEnum));
-
- $intBackedEnum = SupportEnums\MyIntBackedEnum::One;
- $expected = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"One","value":1}';
- $this->assertSame($expected, $this->serializer->serialize($intBackedEnum));
- }
-
- /**
- * Test serialization of multiple Enums
- *
- * @return void
- */
- public function testSerializeMultipleEnums()
- {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped("Enums are only available since PHP 8.1");
- }
-
- $obj = new stdClass();
- $obj->enum1 = SupportEnums\MyUnitEnum::Hearts;
- $obj->enum2 = SupportEnums\MyBackedEnum::Hearts;
- $obj->enum3 = SupportEnums\MyIntBackedEnum::One;
- $obj->enum4 = SupportEnums\MyUnitEnum::Hearts;
- $obj->enum5 = SupportEnums\MyBackedEnum::Hearts;
- $obj->enum6 = SupportEnums\MyIntBackedEnum::One;
-
- $expected = '{"@type":"stdClass","enum1":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"},"enum2":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"},"enum3":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"One","value":1},"enum4":{"@type":"@1"},"enum5":{"@type":"@2"},"enum6":{"@type":"@3"}}';
- $this->assertSame($expected, $this->serializer->serialize($obj));
- }
-
- /**
- * Test unserialization of Enums
- *
- * @return void
- */
- public function testUnserializeEnums()
- {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped("Enums are only available since PHP 8.1");
- }
-
- $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"}';
- $obj = $this->serializer->unserialize($serialized);
- $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyUnitEnum', $obj);
- $this->assertSame(SupportEnums\MyUnitEnum::Hearts, $obj);
-
- $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"}';
- $obj = $this->serializer->unserialize($serialized);
- $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyBackedEnum', $obj);
- $this->assertSame(SupportEnums\MyBackedEnum::Hearts, $obj);
-
- $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"Two","value":2}';
- $obj = $this->serializer->unserialize($serialized);
- $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyIntBackedEnum', $obj);
- $this->assertSame(SupportEnums\MyIntBackedEnum::Two, $obj);
- $this->assertSame(SupportEnums\MyIntBackedEnum::Two->value, $obj->value);
-
- // wrong value of BackedEnum is ignored
- $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"S"}';
- $obj = $this->serializer->unserialize($serialized);
- $this->assertInstanceOf('Zumba\JsonSerializer\Test\SupportEnums\MyBackedEnum', $obj);
- $this->assertSame(SupportEnums\MyBackedEnum::Hearts, $obj);
- $this->assertSame(SupportEnums\MyBackedEnum::Hearts->value, $obj->value);
- }
-
- /**
- * Test unserialization of multiple Enums
- *
- * @return void
- */
- public function testUnserializeMultipleEnums()
- {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped("Enums are only available since PHP 8.1");
- }
-
- $obj = new stdClass();
- $obj->enum1 = SupportEnums\MyUnitEnum::Hearts;
- $obj->enum2 = SupportEnums\MyBackedEnum::Hearts;
- $obj->enum3 = SupportEnums\MyIntBackedEnum::One;
- $obj->enum4 = SupportEnums\MyUnitEnum::Hearts;
- $obj->enum5 = SupportEnums\MyBackedEnum::Hearts;
- $obj->enum6 = SupportEnums\MyIntBackedEnum::One;
-
- $serialized = '{"@type":"stdClass","enum1":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Hearts"},"enum2":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Hearts","value":"H"},"enum3":{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyIntBackedEnum","name":"One","value":1},"enum4":{"@type":"@1"},"enum5":{"@type":"@2"},"enum6":{"@type":"@3"}}';
- $actualObj = $this->serializer->unserialize($serialized);
- $this->assertInstanceOf('stdClass', $actualObj);
- $this->assertEquals($obj, $actualObj);
- }
-
- /**
- * Test unserialization of wrong UnitEnum
- *
- * @return void
- */
- public function testUnserializeWrongUnitEnum() {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped("Enums are only available since PHP 8.1");
- }
-
- // bad case generate Error
- $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyUnitEnum","name":"Circles"}';
- $this->expectException(\Error::class);
- $this->serializer->unserialize($serialized);
- }
-
- /**
- * Test unserialization of wrong BackedEnum
- *
- * @return void
- */
- public function testUnserializeWrongBackedEnum() {
- if (PHP_VERSION_ID < 80100) {
- $this->markTestSkipped("Enums are only available since PHP 8.1");
- }
-
- // bad case generate Error
- $serialized = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportEnums\\\\MyBackedEnum","name":"Circles","value":"C"}';
- $this->expectException(\Error::class);
- $this->serializer->unserialize($serialized);
- }
-
/**
* Test serialization of objects using the custom serializers
*
@@ -479,7 +343,20 @@ public function testSerializationOfClosureWithSuperClosureOnConstructor()
}
$closureSerializer = new SuperClosureSerializer();
+
+ $message = '';
+ set_error_handler(static function (int $errno, string $errstr) use (&$message) {
+ $message = $errstr;
+ }, E_USER_DEPRECATED);
+
$serializer = new JsonSerializer($closureSerializer);
+
+ restore_error_handler();
+ $this->assertSame(
+ 'Passing a ClosureSerializerInterface to the constructor is deprecated and will be removed in 4.0.0. Use addClosureSerializer() instead.',
+ $message
+ );
+
$serialized = $serializer->serialize(
array(
'func' => function () {
@@ -582,7 +459,6 @@ public function testSerializationOfClosureWitMultipleClosures()
// Make sure it was serialized with SuperClosure
$serialized = $serializer->serialize($serializeData);
- echo $serialized;
$this->assertGreaterThanOrEqual(0, strpos($serialized, 'SuperClosure'));
$this->assertFalse(strpos($serialized, 'OpisClosure'));
@@ -613,7 +489,8 @@ public function testUnserializeOfClosureWithoutSerializer()
}
$closureSerializer = new SuperClosureSerializer();
- $serializer = new JsonSerializer($closureSerializer);
+ $serializer = new JsonSerializer();
+ $serializer->addClosureSerializer(new ClosureSerializer\SuperClosureSerializer($closureSerializer));
$serialized = $serializer->serialize(
array(
'func' => function () {
@@ -878,7 +755,10 @@ public function testSerializationOfSplDoublyLinkedList()
* Test serialization of an object with an uninitialized typed property
*
* @return void
+ *
+ * @requires PHP >= 7.4
*/
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('>= 7.4')]
public function testSerializeObjectWithUninitializedTypedProperty()
{
$obj = new \Zumba\JsonSerializer\Test\SupportClasses\UninitializedTypedProperty();
@@ -932,7 +812,10 @@ public function testAllowedClassIsDeserialized(): void
/**
* A class NOT in the allowlist must throw JsonSerializerException and must
* never have its __wakeup() or __destruct() triggered (gadget chain blocked).
+ *
+ * @requires PHP >= 7.4
*/
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('>= 7.4')]
public function testUnlistedClassIsRejectedAndMagicMethodsNotCalled(): void
{
SupportClasses\GadgetClass::$wakeupCalled = false;
@@ -956,6 +839,36 @@ public function testUnlistedClassIsRejectedAndMagicMethodsNotCalled(): void
);
}
+ /**
+ * A class NOT in the allowlist must throw JsonSerializerException and must
+ * never have its __wakeup() or __destruct() triggered (gadget chain blocked).
+ *
+ * @requires PHP < 7.4
+ */
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('< 7.4')]
+ public function testUnlistedClassIsRejectedAndMagicMethodsNotCalled2(): void
+ {
+ SupportClasses\GadgetClassWithoutTypes::$wakeupCalled = false;
+ SupportClasses\GadgetClassWithoutTypes::$destructCalled = false;
+
+ $this->serializer->setAllowedClasses(['stdClass']); // GadgetClass is NOT listed
+
+ $payload = '{"@type":"Zumba\\\\JsonSerializer\\\\Test\\\\SupportClasses\\\\GadgetClassWithoutTypes","command":"id"}';
+
+ try {
+ $this->serializer->unserialize($payload);
+ $this->fail('Expected JsonSerializerException was not thrown.');
+ } catch (JsonSerializerException $e) {
+ $this->assertStringContainsString('not allowed', $e->getMessage());
+ }
+
+ // Ensure neither magic method was executed.
+ $this->assertFalse(
+ SupportClasses\GadgetClassWithoutTypes::$wakeupCalled,
+ '__wakeup() must not be called on a blocked class.'
+ );
+ }
+
/**
* An empty allowlist must block every class, including stdClass.
*/
@@ -997,7 +910,10 @@ public function testSettingAllowedClassesToNullRestoresDefaultBehaviour(): void
/**
* Simulates the PoC from the security report: an attacker-supplied @type
* pointing to a gadget class must be rejected when an allowlist is active.
+ *
+ * @requires PHP >= 7.4
*/
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('>= 7.4')]
public function testSecurityReportPoCIsBlockedByAllowlist(): void
{
$this->serializer->setAllowedClasses(['stdClass']);
@@ -1012,4 +928,26 @@ public function testSecurityReportPoCIsBlockedByAllowlist(): void
$this->expectException(JsonSerializerException::class);
$this->serializer->unserialize($payload);
}
+
+ /**
+ * Simulates the PoC from the security report: an attacker-supplied @type
+ * pointing to a gadget class must be rejected when an allowlist is active.
+ *
+ * @requires PHP < 7.4
+ */
+ #[\PHPUnit\Framework\Attributes\RequiresPhp('< 7.4')]
+ public function testSecurityReportPoCIsBlockedByAllowlist2(): void
+ {
+ $this->serializer->setAllowedClasses(['stdClass']);
+
+ // Payload from the security report (adapted to a class defined in this
+ // test suite so that class_exists() returns true).
+ $payload = json_encode([
+ '@type' => 'Zumba\\JsonSerializer\\Test\\SupportClasses\\GadgetClassWithoutTypes',
+ 'command' => 'id',
+ ]);
+
+ $this->expectException(JsonSerializerException::class);
+ $this->serializer->unserialize($payload);
+ }
}
diff --git a/tests/SupportClasses/GadgetClassWithoutTypes.php b/tests/SupportClasses/GadgetClassWithoutTypes.php
new file mode 100644
index 0000000..6065b42
--- /dev/null
+++ b/tests/SupportClasses/GadgetClassWithoutTypes.php
@@ -0,0 +1,26 @@
+