Skip to content

Prevent serialization of non-backed enums#6796

Open
innerfly wants to merge 8 commits intoCodeception:mainfrom
innerfly:6753-fix-example-serialization-issue
Open

Prevent serialization of non-backed enums#6796
innerfly wants to merge 8 commits intoCodeception:mainfrom
innerfly:6753-fix-example-serialization-issue

Conversation

@innerfly
Copy link

Fixes an #6753, when json_encode fails to serialize non-backed enum.
Follow up #6762 (comment)

@innerfly innerfly changed the title Prevent serialization of non-backed enums #6753 Prevent serialization of non-backed enums Sep 18, 2024
Copy link
Member

@Arhell Arhell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Naktibalda look when you have time, thanks

@W0rma
Copy link
Contributor

W0rma commented Dec 11, 2025

@innerfly Can you try to add a test case?

@innerfly
Copy link
Author

@W0rma Simple test case:

<?php

namespace Unit;

use Codeception\Attribute\DataProvider;
use Codeception\Example;
use PHPUnit\Framework\Assert as Assert;

enum ExampleEnum
{
    case FOO;
    case BAR;
}

class ExampleCest
{
    protected function EnumProvider(): array
    {
        return [
            ['expected' => ExampleEnum::FOO, 'result' => ExampleEnum::BAR],
            ['expected' => ExampleEnum::BAR, 'result' => ExampleEnum::BAR],
        ];
    }

    #[DataProvider('EnumProvider')]
    public function testEnum(Example $example)
    {
        Assert::assertSame($example['expected'], $example['result']);
    }
}

Result:

There was 1 error:
1) ExampleCest: Test enum | "Unit\\ExampleEnum","Unit\\ExampleEnum"
 Test  tests/unit/ExampleCest.php:testEnum
                                                                  
  [JsonException] Non-backed enums have no default serialization  

@burned42
Copy link
Contributor

@innerfly I think what he meant was a test that can be added to the repo showing that this is fixed with your changes and ensures it won't break again by some future changes.

$signature .= ':' . substr(sha1($encoded), 0, 7);
try {
if (method_exists($testCase, 'getMetadata') && $example = $testCase->getMetadata()->getCurrent('example')) {
$encoded = json_encode($example, JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will avoid breaking the whole execution, however this might result in non-unique signatures being returned as the error will just get ignored. I don't know if this is an issue or not.

But I think we could improve this even further by avoiding an exception entirely. We could convert enums manually before passing the example to json_encode. Maybe something like this:

Suggested change
$encoded = json_encode($example, JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE);
$example = self::normalizeForJsonEncoding($example);
$encoded = json_encode($example, JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE);
// ...
}
private static function normalizeForJsonEncoding(mixed $value): mixed
{
return match (true) {
is_array($value) => array_map(self::normalizeForJsonEncoding(...), $value),
$value instanceof UnitEnum => $value->name,
default => $value,
};
}

Copy link
Author

@innerfly innerfly Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@burned42 I have added manual enum convertions following your idea, and test cases to cover this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one minor nitpick, it's not necessary to manually convert backed enums as those can indeed be serialized to json and only the non-backed enums cause issues.

Unfortunately github tells me I don't have the permission to mark my discussions as resolved, but the change itself looks good to me now other than this small note 👍

The test setup looks a bit complicated, but I don't know if that can be simplified. The tests do seem to cover the changed code though :)

Copy link
Author

@innerfly innerfly Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea of adding backed enums before unit enums was to process them before unit enums because techically every enum is UnitEnum (BackedEnum extends UnitEnum). Following
$value instanceof UnitEnum => $value->name,
would convert every enum. Ended up by adding advanced checking, this way it looks more obvious
$value instanceof UnitEnum && !($value instanceof BackedEnum) => $value->name,

@innerfly innerfly force-pushed the 6753-fix-example-serialization-issue branch from 0fa872b to a1684b0 Compare January 21, 2026 09:44
@innerfly innerfly force-pushed the 6753-fix-example-serialization-issue branch from a1684b0 to da50e50 Compare January 21, 2026 10:36
};
}

private static function normalizeForJsonEncoding(mixed $value): mixed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is a bit off here:

Suggested change
private static function normalizeForJsonEncoding(mixed $value): mixed
private static function normalizeForJsonEncoding(mixed $value): mixed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants