Serialize closures declared in constant expressions#22716
Open
nicolas-grekas wants to merge 1 commit into
Open
Serialize closures declared in constant expressions#22716nicolas-grekas wants to merge 1 commit into
nicolas-grekas wants to merge 1 commit into
Conversation
f7d29e5 to
12fc80b
Compare
b1fa8f4 to
8ff4c27
Compare
814db1c to
fae8b5c
Compare
arnaud-lb
reviewed
Jul 14, 2026
| uint8_t found_kind; | ||
| zend_string *found_name; | ||
| uint8_t found_hook; | ||
| } zend_constexpr_closure_walk; |
Member
There was a problem hiding this comment.
This struct could use a union to show which fields are relevant for FCCs vs anonymous classes
Contributor
Author
There was a problem hiding this comment.
on a closer look, not sure it's worth it
| ZEND_PARSE_PARAMETERS_NONE(); | ||
|
|
||
| if (zend_constexpr_closure_ref(Z_OBJ_P(ZEND_THIS), &ce, &id) == FAILURE) { | ||
| zend_throw_exception(NULL, "Serialization of 'Closure' is not allowed", 0); |
Member
There was a problem hiding this comment.
To avoid confusions, we could specify that this is allowed in some cases
Contributor
Author
There was a problem hiding this comment.
added a comment about this: I don't think changing the userland message is worth it
fae8b5c to
04c14b1
Compare
74ac4be to
1590605
Compare
1590605 to
fffacae
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC: https://wiki.php.net/rfc/serializable_closures
Closures declared in constant expressions of a class member (anonymous closures and first-class callables, of any visibility) become serializable as references to their declaration site.
unserialize()resolves the reference against the loaded class; payloads contain no code and can only designate closures that classes declare. Runtime-created closures keep refusing to serialize with the current error.Adds
Closure::fromConstExpr()andReflectionFunction::getConstExprId()/getConstExprClass()for exporters.The reference is an opaque "@" string naming the declaring element (class, constant, property, property hook, or method, with parameters folded into their method) and a key within it, so it stays valid when unrelated elements of the class change. For an anonymous closure the key is its rank among the element's closures; for a first-class callable it is the callable's name ("@called::method" / "@function"), since the name is already the address.
The
__serialize()payload is[<object properties>, [<tag>, <reference>]](the shape ext/uri and ext/random use), where the tag names the reference kind; only "const-expr" exists today. An anonymous-closure id embeds a hash of the closure's pretty-printed code ("$billingAddress@0#4a3f"), computed at compile time and stored in spare bits of the declaration's AST node, so a rank that comes to name a different closure (e.g. after two attributes on the same member are swapped) fails loudly instead of resolving silently. Bothunserialize()andClosure::fromConstExpr()verify it, so exporter-generated cache files get the same protection as serialized payloads. Producers that cannot compute the hash (userland polyfills) may omit the "#" suffix; such references resolve positionally, unverified. First-class callable ids carry no hash: a name cannot drift.