Table of Contents

PHP RFC: Add #[NoSerialize] attribute for excluding properties or classes from serialization

Introduction

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.

Proposal

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.

1. Syntax and Definition

<?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";}`.
?>

2. Semantics

2.1 Property-level Behavior

When the #[NoSerialize] attribute is applied to a property, it instructs the engine to skip that property during native serialization.

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"
}
*/
Interaction with __serialize() and __sleep()

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;
    }
}
Inheritance and Traits:
class Example
{
    public function __construct(
        public string $name,
        #[NoSerialize]
        public ?PDO $db = null
    ) {}
}

2.2 Class-level Behavior

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:

Inheritance:

The prohibition is permanent (“sticky”) and automatically propagated throughout the inheritance chain.

2.3 Interaction with other serialization forms

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.

2.4 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.5 Invalid Targets & Compile-Time Diagnostics

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:

3. Alternative names

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.

Backward Incompatible Changes

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.

Proposed PHP Version(s)

Next version of PHP (PHP 8.6 or PHP 9.0)

RFC Impact

To the Ecosystem

This RFC has no negative impact on existing code and benefits frameworks, static analyzers, and serializers that rely on native PHP serialization.

To Existing Extensions

None

To SAPIs

None

Open Issues

None currently.

Future Scope

Voting Choices

Implement #[NoSerialize] attribute as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

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

Rejected Features

Changelog