Serialization is a fundamental PHP mechanism 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.
This proposal introduces a new #[NoSerialize] attribute that can be applied to properties to exclude them from native PHP serialization or to classes to forbid serialization entirely.
It provides a declarative alternative to manually filtering properties within __sleep() or __serialize(), making serialization rules easier to maintain and more self-documenting.
<?php use Attribute; /** * Marks a property or class as excluded from native serialization. * * When applied to a property, it will be skipped during serialize(). * When applied to a class, serialization will be forbidden. */ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] 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";}`. ?>
When the #[NoSerialize] attribute is applied to a property, it instructs the engine to skip that property during native serialization.
serialize() is invoked and the class does not define its own __serialize() or __sleep(), properties marked with #[NoSerialize] are skipped automatically.unserialize(), all properties present in the serialized payload are restored normally.#[NoSerialize] attribute does not affect deserialization — if a property exists in the serialized data (for example, from an older class version or custom serializer), it will still be deserialized.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" } */
If a class defines its own serialization logic via __serialize() or __sleep(), the #[NoSerialize] attribute on properties has no effect.
These methods are entirely user-defined, and PHP does not automatically filter out properties or classes marked with #[NoSerialize].
This design maintains explicit and consistent behavior with existing PHP semantics — developer-defined serialization always takes precedence.
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 respect #[NoSerialize] inside __serialize() can do so manually via 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)) { $result[$prop->getName()] = $prop->getValue($this); } } return $result; } }
#[NoSerialize] applied to a property affects only that declaration and is not inherited if a subclass redeclares the property.class Example { public function __construct( public string $name, #[NoSerialize] public ?PDO $db = null ) {} }
When the #[NoSerialize] attribute is applied to a class, any attempt to serialize an instance of that class will throw an exception, explicitly forbidding its serialization.
This behavior uses the same internal mechanism as for built-in non-serializable classes (such as Random\Engine\Secure or CurlHandle).
This ensures that invalid or unintended serialization attempts are immediately visible to developers and cannot result in partial or lossy data structures.
#[NoSerialize] class Connection { public PDO $pdo; public function __construct() { $this->pdo = new PDO('sqlite::memory:'); } } class Wrapper { public string $name = 'foo'; public Connection $conn; public function __construct() { $this->conn = new Connection(); } } $w = new Wrapper(); echo serialize($w); /* Fatal error: Cannot serialize instance of class Connection marked with #[NoSerialize] */
Notes:
#[NoSerialize] forbids serialization entirely by throwing a fatal error.#[NoSerialize] is inherited by all child classes and cannot be “overridden”.The prohibition is permanent (“sticky”) and automatically propagated throughout the inheritance chain.
#[NoSerialize] again in a subclass when the parent already has it is a no-op (but allowed).serialize(), unserialize()).
Future changes extending the behavior of #[NoSerialize] to json_encode() or other formats would be backward-incompatible once this RFC is implemented.
For that reason, any future proposal in this direction would need to introduce a separate attribute, such as #[NoJsonEncode], to avoid ambiguity and preserve expected behavior for existing code.
The attribute is visible and queryable via reflection:
$rp = new ReflectionProperty(Example::class, 'connection'); var_dump($rp->getAttributes(NoSerialize::class)); // array(1) { ... }
Applying #[NoSerialize] to unsupported targets results in compile-time diagnostics.
The engine validates the attribute’s target during class compilation and emits appropriate compile-time errors.
| Target | Severity | Message | Behavior |
|---|---|---|---|
| Static property | E_COMPILE_ERROR | `Cannot apply #[\NoSerialize] to static property %s::$%s` | Compilation aborted |
| Virtual property | E_COMPILE_ERROR | `Cannot apply #[\NoSerialize] to virtual property %s::$%s` | Compilation aborted |
| Interface | E_COMPILE_ERROR | `Cannot apply #[\NoSerialize] to interface %s` | Compilation aborted |
| Trait | E_COMPILE_ERROR | `Cannot apply #[\NoSerialize] to trait %s` | Compilation aborted |
Rationale:
| Proposed name | Notes |
|---|---|
| NoSerialize | Chosen for its clarity and consistency. Short, imperative, and self-explanatory. |
| SkipSerialize | Grammatically clear and intuitive; “skip” emphasizes runtime behavior rather than prohibition. Could be a valid alternative if “NoSerialize” is considered stylistically inconsistent. |
| SerializeIgnore | Mirrors conventions used in other languages and frameworks (e.g., @JsonIgnore in Java). However, it feels less idiomatic in PHP, which favors simple verb prefixes (No*, Allow*, etc.) over noun-based ones. |
| DoNotSerialize | Verbose but explicit. Deemed unnecessarily long for PHP attribute syntax. |
Defining a userland class named NoSerialize in the global namespace will no longer be possible, as this name becomes reserved for the new attribute.
A GitHub search for “class NoSerialize ” language:php returned 11 results, all defined within namespaces. Therefore, this change would not affect any known public codebases in practice, and the impact on backward compatibility is expected to be negligible.
Next version of PHP (PHP 8.6 or PHP 9.0)
This RFC has no negative impact on existing code and benefits frameworks, static analyzers, and serializers that rely on native PHP serialization.
None
None
None currently.
__sleep() to return null or no value, signaling the engine to fall back to the default serialization logic, which would then automatically respect #[NoSerialize].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