====== 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.