PHP RFC: Add #[NoSerialize] attribute for excluding properties from serialization
- Version: 0.1
- Date: 2025-10-06
- Author: Dmytro Kulyk, lnkvisitor.ts@gmail.com
- Status: Draft
- Implementation: https://github.com/php/php-src/pull/20074
Introduction
Serialization is a fundamental mechanism in PHP that allows objects to be converted into a storable or transferable representation.
However, not every property of an object should necessarily be serialized. Frameworks and libraries often contain transient or resource-based properties—such as database connections, file handles, or caches—that should not be persisted.
Currently, developers must manually handle this by overriding __sleep()
or __serialize()
, which can lead to repetitive boilerplate and maintenance overhead.
Proposal
This proposal introduces a new #[NoSerialize]
attribute that can be applied to class properties to exclude them from native PHP serialization explicitly.
It provides a declarative alternative to manually filtering properties within __sleep()
or __serialize()
, making serialization rules easier to maintain and more self-documenting.
1. Syntax and Definition
<?php use Attribute; /** * Marks a property as excluded from native serialization. * * When applied, this property will not appear in the output * of serialize(), nor be restored during unserialize(). */ #[Attribute(Attribute::TARGET_PROPERTY)] final class NoSerialize {} ?>
Usage example:
<?php class Example { public string $name; #[NoSerialize] public PDO $connection; } $object = new Example(); $object->name = "User"; $object->connection = new PDO('sqlite::memory:'); echo serialize($object); // Serialized output will be `O:7:"Example":1:{s:4:"name";s:4:"User";}`. ?>
2. Semantics
2.1 Behavior in Native Serialization
- When
serialize()
is invoked and the class does not define its own__serialize()
or__sleep()
, properties marked with#[NoSerialize]
are skipped automatically. - The resulting serialized data omits these properties entirely.
- Upon unserialize(), all properties present in the serialized payload are restored normally.
- The
#[NoSerialize]
attribute does not affect deserialization — if a property exists in the serialized data (e.g., produced by an older version of the class or by userland code), it will be deserialized. - Skipped properties (those not present in the data) remain uninitialized if they have no default, or are restored to their declared default value.
class SessionWrapper { public string $id; #[NoSerialize] public $resource; // transient field public function __construct() { $this->resource = fopen('php://memory', 'r+'); } } $s = new SessionWrapper(); $s->id = 'abc'; var_dump(unserialize(serialize($s))); /* object(SessionWrapper)#2 (1) { ["id"]=> string(3) "abc" } */
2.2 Interaction with __serialize() and __sleep()
If a class defines its own serialization logic via __serialize()
or __sleep()
, the #[NoSerialize]
attribute has no effect.
These methods are entirely user-defined, and PHP does not automatically filter out attributes marked with #[NoSerialize]
.
This design maintains explicit and consistent behavior with existing PHP semantics — developer code always takes precedence when custom serialization is implemented.
Example:
class Custom { public string $a = 'A'; #[NoSerialize] public string $b = 'B'; public function __serialize(): array { return ['a' => $this->a, 'b' => $this->b]; } } echo serialize(new Custom()); // Output still contains both 'a' and 'b'
Developers who wish to honor #[NoSerialize]
within a custom __serialize()
can do so manually using reflection:
class Custom { public string $a = 'A'; #[NoSerialize] public string $b = 'B'; public function __serialize(): array { $result = []; foreach ((new ReflectionObject($this))->getProperties() as $prop) { if (!$prop->getAttributes(NoSerialize::class)) { $prop->setAccessible(true); $result[$prop->getName()] = $prop->getValue($this); } } return $result; } }
2.3 Inheritance and Traits
#[NoSerialize]
applies only to the declaring property and is not inherited when a child class redeclares the same property.- Properties introduced via traits preserve the attribute when the trait is used.
- Promoted constructor properties can include the attribute as usual:
class Example { public function __construct( public string $name, #[NoSerialize] public ?PDO $db = null ) {} }
2.4 Interaction with other serialization forms
- The attribute affects only native PHP serialization (
serialize()
,unserialize()
).
2.5 Reflection API
The attribute is visible and queryable via reflection:
$rp = new ReflectionProperty(Example::class, 'connection'); var_dump($rp->getAttributes(NoSerialize::class)); // array(1) { ... }
2.6 Invalid Targets & Compile-Time Diagnostics
Applying #[NoSerialize]
to static or virtual properties is not meaningful for native serialization. The engine will therefore emit compile-time warnings and ignore the attribute in these cases.
Engine messages (proposed):
- Static property %s::$%s is not serializable
- Virtual property %s::$%s is not serializable
Backward Incompatible Changes
NoSerialize can no longer be used as a class name in the global namespace. A GitHub search for “class NoSerialize” language:php revealed only one match in source code. The class is defined within a namespace.
Proposed PHP Version(s)
Next version of PHP (PHP 8.6 or PHP 9.0)
RFC Impact
To the Ecosystem
What effect will the RFC have on IDEs, Language Servers (LSPs), Static Analyzers, Auto-Formatters, Linters and commonly used userland PHP libraries?
To Existing Extensions
None
To SAPIs
None
Open Issues
Make sure there are no open issues when the vote starts!
Future Scope
- #[NotSerializable] — a class-level attribute that forbids any instance serialization (throws an error).
- Allow __sleep() to return null or no value, signaling the engine to fall back to the default serialization logic, which would then automatically respect #[NoSerialize].
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.