====== 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 ==== Usage example: 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 === * Out of scope: JSON (json_encode() / JsonSerializable) and var_export() remain unaffected. * 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 [[https://github.com/php/policies/blob/main/feature-proposals.rst#voting|the php/policies repository]] for the current voting guidelines. * Yes * No * Abstain ===== Patches and Tests ===== [[https://github.com/php/php-src/pull/20074]] ===== 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.