Repository navigation
Blocks: Return an empty array for a non-string document in WP_Block_Parser::parse(). - #15340
rejaulalomkhan wants to merge 3 commits into
Conversation
Since [64870], parse() passes its document to the WP_Block_Processor constructor, which is typed to accept only a string. A null document therefore causes a fatal TypeError in parse_blocks(), do_blocks(), and anything hooked to the_content, where previously the parser returned an empty list of blocks. Follow-up to [64870]. See #66138.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. Props Bot commandsYou can trigger Props Bot actions by commenting on this PR:
|
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
dmsnell
left a comment
There was a problem hiding this comment.
Thanks for noting this. I do see that passing NULL previously would generate a warning, and passing some non-string values would actually crash the old parser.
We can improve this without widening the type however, which might lead to a better system where people avoid sending null inputs, while still returning a non-crashing value if they do.
| * @since 5.0.0 | ||
| * | ||
| * @param string $document Input document being parsed. | ||
| * @param string|null $document Input document being parsed. Null is treated as an empty document. |
There was a problem hiding this comment.
this represents defensive behavior that can unintentionally complicate the interface and encourage passing NULL, when we’ve always wanted the contract to be passing a string.
non-null values should have previously all returned an empty array, so perhaps instead of inviting passing null, how about we keep the type as it is, but provide a default empty array, adding a _doing_it_wrong()
that would highlight to developers not to pass non-string values while retaining the fallback behavior
There was a problem hiding this comment.
Agreed, reverting this documentation change would be good:
| * @param string|null $document Input document being parsed. Null is treated as an empty document. | |
| * @param string $document Input document being parsed. |
There was a problem hiding this comment.
Thanks, @dmsnell. Done in 39ccd66: @param stays string, and a non-string document now returns an empty array with a _doing_it_wrong() notice.
One difference from what you described: on trunk, parse_blocks( 123 ) doesn't return an empty array. The integer is converted to a string and comes back as a freeform block with 123 as its content. This change makes it return an empty array with the notice, like the other non-strings. I didn't check that on 7.1. If you'd rather leave scalars as they are, I can narrow the check.
@sirreal, this also covers your suggestion to restore the @param string line.
There was a problem hiding this comment.
returning an empty array is probably okay for 123. there’s no contract where passing a non-string value returns block structure.
| * @param string $document Input document being parsed. | ||
| * @return array[] | ||
| */ | ||
| public function parse( $document ) { |
There was a problem hiding this comment.
I don’t know if we should expect Stringable objects here, but it might help to add a check for this
if ( is_object( $document ) && method_exists( $document, '__toString' ) ) {
_doing_it_wrong( ..., __( 'Convert to string before calling `parse()`' ), ... );
$document = (string) $document;
}thoughts? perhaps see if you can find evidence of any code in Core or int he plugin directory that passes non-string-but-Stringable values into parse_blocks()
There was a problem hiding this comment.
Thanks, @dmsnell. I didn't find anyone passing a Stringable, but I'd still add your check, because this PR changes what happens if someone does. A Stringable document parses normally on 7.1 and trunk, since PHP converts it to a string. With the PR as it stands, it returns an empty array.
What I checked:
- Core: I found no
parse_blocks()caller that passes an object. They pass post content, template or pattern content, or markup strings.do_blocks()passes on whateverthe_contentreceives, so a plugin could still reach it that way. - Plugin directory: I searched it with Veloria and found about 4,800 lines that look like
parse_blocks()calls. None pass$this,new ...,Str::...orstr(...), or a variable named like an HTML processor.do_blocks()had no matches for those patterns either, and neither did the theme directory. - Limits: this is a text search. Most of those calls pass a variable or property, and I can't tell the runtime type from the line. So this shows no obvious use, not that there is none.
Done in afa82b1: a Stringable document now triggers the notice and is converted and parsed. I kept is_object() with method_exists() as you wrote it, because the minimum PHP version is below 8.0 and the template loader and patterns registry use the same check.
Also, to close the question from my earlier comment: I've now checked 7.1, and 123 comes back as a freeform block there too, the same as trunk.
A stringable object parses normally on 7.1 and trunk because PHP converts it to a string. Convert it explicitly and trigger a notice, rather than returning an empty array. See #66138.
Trac ticket: Core-66138
Description
Since r64870,
WP_Block_Parser::parse()passes its document to theWP_Block_Processorconstructor, which accepts only a string. A null document now causes a fatalTypeErrorinparse_blocks(),do_blocks(),filter_block_content(), andapply_filters( 'the_content', null ).The 7.1 parser returns an empty list of blocks for the same input, with two "Passing null" deprecation notices, so this is a regression for any plugin or theme that passes null. One way to reach it in core is a REST request that omits a Block widget's content, for example
PUT /wp/v2/widgets/block-2withform_data=widget-block[2][_x]=1.WP_Widget_Block::update()storescontentas null, the request fails with theTypeError, anddynamic_sidebar()keeps failing until the value is repaired.This keeps the
stringtype onparse(). A document that is not a string now returns an empty array and triggers a_doing_it_wrong()notice, so developers see the mistake and the site doesn't fail. AStringableobject also triggers a notice, but it is converted to a string and parsed.This also changes other scalars. An integer used to be converted to a string and parsed, and it now returns an empty array with the notice.
Not changed here:
WP_Widget_Block::update()still readscontentwithout a guard, andfilter_block_content()and otherthe_contentcallbacks can still raise their own deprecation notices for null.Testing Instructions
null,false, an integer, an array, and an object) and aStringabledocument:vendor/bin/phpunit --filter 'test_returns_empty_array_for_non_string_document|test_parses_stringable_document_as_string'Results on PHP 8.3:
blocks724 tests and 3472 assertions,block-processor279 and 822,widgets505 and 2016, multisiteblocks723 and 3462, all passing. PHPCS is clean. PHPStan reports one error,$authors_dropdowninclass-wp-posts-list-table.php, which is also present on trunk without this change.Trac ticket: https://core.trac.wordpress.org/ticket/66138
This Pull Request is for code review only. Please keep all other discussion to the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: root-cause analysis, drafting the fix and regression test, running the checks listed above, and drafting this description. I reviewed every line and take responsibility for the change.