PHP already has a robust engine-level mechanism for forbidding serialization of certain types (e.g., closures, internal classes). This RFC proposes exposing that capability to userland with a simple, declarative attribute:
<?php #[NotSerializable] final class MyService { // … }
When applied, attempts to serialize()
(or unserialize(
) into) such a class will be rejected by the engine, without invoking __sleep()
, __serialize()
, or legacy Serializable
.
Note: json_encode()
/JsonSerializable
and var_export()
are out of scope for this RFC and remain unaffected.
__serialize()
/__sleep()
or via the deprecated Serializable
interface. The attribute is cleaner and future-proof.
Introduce a built-in attribute #[\NotSerializable]
:
<?php #[Attribute(Attribute::TARGET_CLASS)] final class NotSerializable {}
serialize($obj)
:
If $obj
is an instance of a class (or enum) marked #[NotSerializable]
, the engine throws:
Serialization of 'ClassName' is not allowed.
unserialize($str)
:
If the payload contains an instance of a #[NotSerializable]
class, the engine throws:
Unserialization of 'ClassName' is not allowed.
#[NotSerializable]
to a subclass when the parent already has it is a no-op (but allowed).The attribute cannot be applied to interfaces or traits. Doing so is a compile-time error.
The attribute is discoverable via ReflectionClass::getAttributes(NotSerializable::class)
.
No new reflection APIs are proposed.
<?php #[NotSerializable] final class TokenBucket { public function __construct( private int $capacity, private float $refillRatePerSecond, ) {} } serialize(new TokenBucket(10, 1.5)); // Exception: Serialization of 'TokenBucket' is not allowed ?>
<?php #[NotSerializable] class Service {} class SpecializedService extends Service {} serialize(new SpecializedService()); // Exception: Serialization of 'SpecializedService' is not allowed
<?php #[NotSerializable] enum HandleState: string { case Open = 'open'; case Closed = 'closed'; } serialize(HandleState::Open); // Exception: Serialization of 'HandleState' is not allowed
==== Interface:
<?php #[NotSerializable] interface IDBService {} //Fatal error: Cannot apply #[\NotSerializable] to interface IDBServic ?>
The NotSerializable class name can no longer be used in the global namespace. A GitHub search for “class NotSerializable ” language:php revealed a total of 28 matches in source code. Almost all of them are in the tests.
PHP 8.6
No breaking changes, but provides new metadata that advanced tools can optionally use for safer analysis and diagnostics
None.
None
Make sure there are no open issues when the vote starts!
This section should outline areas that you are not planning to work on in the scope of this RFC, but that might be iterated upon in the future by yourself or another contributor.
This helps with long-term planning and ensuring this RFC does not prevent future work.
Pick a title that reflects the concrete choice people will vote on.
Please consult the php/policies repository for the current voting guidelines.
After the RFC is implemented, this section should contain:
Links to external references, discussions, or RFCs.
Keep this updated with features that were discussed on the mail lists.
If there are major changes to the initial proposal, please include a short summary with a date or a link to the mailing list announcement here, as not everyone has access to the wikis' version history.