Skip to content

Bug: Entity::normalizeValue incorrectly handles enums when they implement toArray() #10136

@maniaba

Description

@maniaba

PHP Version

8.3

CodeIgniter4 Version

4.7.2

CodeIgniter4 Installation Method

Composer (using codeigniter4/appstarter)

Which operating systems have you tested for this bug?

Linux

Which server did you use?

apache

Environment

development

Database

Not relevant

What happened?

When using CodeIgniter\Entity\Entity::injectRawData() with a PHP enum (UnitEnum / BackedEnum), an infinite recursion or unexpected behavior occurs if the enum defines a toArray() method.

This is caused by the order of checks in Entity::normalizeValue():

} elseif (method_exists($data, 'toArray')) {
    $objectData = $data->toArray();
} elseif ($data instanceof UnitEnum) {

Because method_exists($data, 'toArray') is evaluated before checking for UnitEnum, enums that define a toArray() method are treated as generic objects instead of enums.

This leads to recursive normalization and may result in infinite loops or excessive processing.

Steps to Reproduce

  1. Create an enum with a toArray() method:
enum TestRawEnum: string
{
    case TEST = 'test';
    case IN_REVIEW = 'in_review';

    public function toArray(): array
    {
        return [
            self::TEST,
            self::IN_REVIEW,
        ];
    }
}
  1. Use it in an Entity:
$e = new \CodeIgniter\Entity\Entity();

$e->injectRawData([
    'test' => TestRawEnum::TEST,
]);

This triggers normalizeValue() internally via syncOriginal().

Expected Output

Enums should always be handled by the UnitEnum branch: elseif ($data instanceof UnitEnum) regardless of whether they implement toArray() or not.

Actual Behavior:

Enums with a toArray() method are processed by method_exists($data, 'toArray') instead of the UnitEnum handler, causing incorrect normalization and potential recursion issues.

Proposed Fix:

Change the order of checks in normalizeValue() so that enums are handled before toArray():

if ($data instanceof self) {
    ...
} elseif ($data instanceof UnitEnum) {
    return [
        '__class' => $data::class,
        '__enum'  => $data instanceof BackedEnum ? $data->value : $data->name,
    ];
} elseif ($data instanceof JsonSerializable) {
    ...
} elseif (method_exists($data, 'toArray')) {
    $objectData = $data->toArray();
}

Anything else?

This issue appears whenever enums are extended with helper methods like toArray(), which is a common pattern.

The current implementation assumes that presence of toArray() means the object should be treated as a generic value object, which conflicts with PHP enum behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugVerified issues on the current code behavior or pull requests that will fix them

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions