Skip to content

Blocks: Return an empty array for a non-string document in WP_Block_Parser::parse(). - #15340

Open
rejaulalomkhan wants to merge 3 commits into
WordPress:trunkfrom
rejaulalomkhan:fix/66138-parse-blocks-null
Open

rejaulalomkhan wants to merge 3 commits into
WordPress:trunkfrom
rejaulalomkhan:fix/66138-parse-blocks-null

Conversation

@rejaulalomkhan

@rejaulalomkhan rejaulalomkhan commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Trac ticket: Core-66138

Description

Since r64870, WP_Block_Parser::parse() passes its document to the WP_Block_Processor constructor, which accepts only a string. A null document now causes a fatal TypeError in parse_blocks(), do_blocks(), filter_block_content(), and apply_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-2 with form_data=widget-block[2][_x]=1. WP_Widget_Block::update() stores content as null, the request fails with the TypeError, and dynamic_sidebar() keeps failing until the value is repaired.

This keeps the string type on parse(). 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. A Stringable object 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 reads content without a guard, and filter_block_content() and other the_content callbacks can still raise their own deprecation notices for null.

Testing Instructions

  1. Run the new tests, which cover five non-string data sets (null, false, an integer, an array, and an object) and a Stringable document:
    vendor/bin/phpunit --filter 'test_returns_empty_array_for_non_string_document|test_parses_stringable_document_as_string'
  2. Run the related groups on single site:
    vendor/bin/phpunit --group blocks
    vendor/bin/phpunit --group block-processor
    vendor/bin/phpunit --group widgets
  3. Run the blocks group on multisite:
    vendor/bin/phpunit -c tests/phpunit/multisite.xml --group blocks
  4. Run the static checks:
    vendor/bin/phpcs src/wp-includes/class-wp-block-parser.php tests/phpunit/tests/blocks/wpBlockParser.php
    composer phpstan

Results on PHP 8.3: blocks 724 tests and 3472 assertions, block-processor 279 and 822, widgets 505 and 2016, multisite blocks 723 and 3462, all passing. PHPCS is clean. PHPStan reports one error, $authors_dropdown in class-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.

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.
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

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:

Props rejaulalomkhan, dmsnell, jonsurrell.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@dmsnell dmsnell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, reverting this documentation change would be good:

Suggested change
* @param string|null $document Input document being parsed. Null is treated as an empty document.
* @param string $document Input document being parsed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returning an empty array is probably okay for 123. there’s no contract where passing a non-string value returns block structure.

@rejaulalomkhan rejaulalomkhan changed the title Blocks: Treat a null document as empty in WP_Block_Parser::parse(). Blocks: Return an empty array for a non-string document in WP_Block_Parser::parse(). Nov 10, 2026
* @param string $document Input document being parsed.
* @return array[]
*/
public function parse( $document ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 whatever the_content receives, 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::... or str(...), 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants