Skip to content

Native PHP enums serialize as opaque 'Object <ClassName>' in stack traces #2037

@jarstelfox

Description

@jarstelfox

Description

Native PHP enums (available since PHP 8.1) are serialized as opaque Object <ClassName> strings in Sentry error reports, losing their value entirely. For example, a BackedEnum like:

enum AttributeSource: string {
    case AKENEO = 'akeneo';
    case NETSUITE = 'netsuite';
}

Shows up as Object iFixit\Admin\Akeneo\AttributeSource in stack trace variable dumps — with no indication of which case was active.

Root Cause

AbstractSerializer::serializeRecursively() treats enums as generic objects. Since native enums don't implement SerializableInterface, they fall through to serializeValue(), which outputs 'Object ' . $reflection->getName(). Enums also lack a public id property or getId() method, so they don't even get the (#id) suffix.

if (\is_object($value)) {
$classSerializers = $this->resolveClassSerializers($value);
// Try each serializer until there is none left or the serializer returned data
foreach ($classSerializers as $classSerializer) {
try {
$serializedObjectData = $classSerializer($value);
if (\is_array($serializedObjectData)) {
return [
'class' => \get_class($value),
'data' => $this->serializeRecursively($serializedObjectData, $_depth + 1),
];
}
} catch (\Throwable $e) {
// Ignore any exceptions generated by a class serializer
}
}
if ($value instanceof \DateTimeInterface) {
return $this->formatDate($value);
}
if ($this->serializeAllObjects || ($value instanceof \stdClass)) {
return $this->serializeObject($value, $_depth);
}
}
return $this->serializeValue($value);

Suggested Fix

Add a check for UnitEnum in serializeRecursively(), before the generic object handling:

if ($value instanceof \UnitEnum) {
    return $value instanceof \BackedEnum
        ? $value->value
        : $value->name;
}

Or alternatively in serializeValue():

if ($value instanceof \UnitEnum) {
    $enumValue = $value instanceof \BackedEnum ? $value->value : $value->name;
    return $reflection->getName() . '::' . $value->name . '(' . $enumValue . ')';
}

Workaround

We're currently working around this by having our enums implement SerializableInterface:

enum AttributeSource: string implements SerializableInterface {
    case AKENEO = 'akeneo';
    case NETSUITE = 'netsuite';

    public function toSentry(): array {
        return [$this->value];
    }
}

This works but requires touching every enum individually.

Environment

  • sentry/sentry: ^4.10.0
  • PHP: 8.4

Metadata

Metadata

Assignees

No one assigned
    No fields configured for issues without a type.

    Projects

    Status

    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions