PHP RFC: #[\NotSerializable] attribute
- Version: 0.9
- Date: 2025-10-14
- Author: Dmytro Kulyk, lnkvisitor.ts@gmail.com
- Status: Draft
- Implementation: https://github.com/php/php-src/pull/20163
Introduction
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.
Motivation
- Security hardening. Prevent accidental participation in object-serialization gadget chains and reduce risk of sensitive data leakage via PHP’s native serialization format.
- Explicit API intent. Many classes are not meaningful outside process memory (e.g., services, handles, runtime registries). The attribute communicates and enforces this.
- Parity with engine capabilities. The engine already supports a “not serializable” class flag; this RFC introduces a userland switch to set it.
- Lower maintenance. Today, projects emulate “non-serializable” by throwing in
__serialize()
/__sleep()
or via the deprecatedSerializable
interface. The attribute is cleaner and future-proof.
Proposal
Introduce a built-in attribute #[\NotSerializable]
:
<?php #[Attribute(Attribute::TARGET_CLASS)] final class NotSerializable {}
Semantics
- On serialization
serialize($obj)
:
If $obj
is an instance of a class (or enum) marked #[NotSerializable]
, the engine throws:
Serialization of 'ClassName' is not allowed.
- On unserialization
unserialize($str)
:
If the payload contains an instance of a #[NotSerializable]
class, the engine throws:
Unserialization of 'ClassName' is not allowed.
- Existing allowed_classes behavior remains unchanged and is applied before this check.
Inheritance:
- The attribute is inherited by child classes and cannot be “overridden” (i.e., prohibition is sticky).
- Applying
#[NotSerializable]
to a subclass when the parent already has it is a no-op (but allowed).
Interfaces & traits:
The attribute cannot be applied to interfaces or traits. Doing so is a compile-time error.
Reflection:
The attribute is discoverable via ReflectionClass::getAttributes(NotSerializable::class)
.
No new reflection APIs are proposed.
Examples
Basic usage
<?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 ?>
Inheritance:
<?php #[NotSerializable] class Service {} class SpecializedService extends Service {} serialize(new SpecializedService()); // Exception: Serialization of 'SpecializedService' is not allowed
Enums:
<?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 ?>
Backward Incompatible Changes
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.
Proposed PHP Version(s)
PHP 8.6
RFC Impact
To the Ecosystem
No breaking changes, but provides new metadata that advanced tools can optionally use for safer analysis and diagnostics
To Existing Extensions
None.
To SAPIs
None
Open Issues
Make sure there are no open issues when the vote starts!
Future Scope
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.
Voting Choices
Pick a title that reflects the concrete choice people will vote on.
Please consult the php/policies repository for the current voting guidelines.
Patches and Tests
Implementation
After the RFC is implemented, this section should contain:
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
References
Links to external references, discussions, or RFCs.
Rejected Features
Keep this updated with features that were discussed on the mail lists.
Changelog
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.