Skip to content

View config: reject shape-mismatched merges, define empty-array semantics, strip nulls from appended members#12644

Closed
jorgefilipecosta wants to merge 4 commits into
WordPress:trunkfrom
jorgefilipecosta:fix/view-config-merge-shape-mismatch
Closed

View config: reject shape-mismatched merges, define empty-array semantics, strip nulls from appended members#12644
jorgefilipecosta wants to merge 4 commits into
WordPress:trunkfrom
jorgefilipecosta:fix/view-config-merge-shape-mismatch

Conversation

@jorgefilipecosta

@jorgefilipecosta jorgefilipecosta commented Jul 22, 2026

Copy link
Copy Markdown
Member

What?

Follow up to #12638. Gutenberg PR: WordPress/gutenberg#80571.

Fixes three silent data-loss defects in WP_View_Config_Data's merge engine:

  • An associative patch value over a list (or a non-empty list over an associative value) discarded the whole current value. It is now rejected with _doing_it_wrong() and the current value is kept.
  • An empty array under merge() wiped associative values but no-oped on lists. It is now a no-op for both shapes — clear a list with replace() and an empty list, reset a key with null.
  • A list member appended by merge() kept nested nulls that every other write path drops. Appended members now go through strip_nulls().

How?

  • merge_properties(): a patch value only merges into a current value of the same shape. Non-empty mismatches warn and keep the current value; an empty array merges nothing. Empty arrays stay exempt from the guards, so an associative patch still creates new nested maps.
  • merge_list_by_identity(): appended members go through strip_nulls().
  • Docblocks document the rule.

Testing

Verify unit tests are passing:

npm run test:php -- --filter Tests_View_Config_Data

One new test per fix; the inline PR comments show each assertion's result without the fix.

AI usage disclosure: issues found via AI-assisted code review; fix, tests, and description drafted with AI assistance (Claude Code) and reviewed by me.

Trac ticket: https://core.trac.wordpress.org/ticket/65577

merge_properties() classified patch values by shape but let a mismatch
fall through: an associative patch value landing on a list (or a list
landing on an associative value) silently discarded the current value
before merging into nothing, and an empty array — classified as a list —
wiped associative values while no-oping on lists. An unmatched list
member was also appended verbatim, storing nested nulls that every other
write path consumes.

A non-empty shape mismatch is now reported with _doing_it_wrong() and
leaves the current value unchanged, an empty array under merge() is a
documented no-op (clearing stays with replace() and null), and appended
list members have their nulls stripped like every other path with no
existing leaf to delete.

See #65577.
Copilot AI review requested due to automatic review settings July 22, 2026 13:27
@github-actions

github-actions Bot commented Jul 22, 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. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props jorgefilipecosta, mukesh27, oandregal.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This pull request hardens WP_View_Config_Data’s patch merge behavior to prevent silent configuration loss and schema-violating output when merge patches don’t match the current value’s shape, aligning server behavior with the documented patch contract and the referenced Gutenberg fix.

Changes:

  • Reject non-empty shape mismatches during merge() (associative-over-list and list-over-associative) via _doing_it_wrong() while leaving the current value unchanged.
  • Treat empty arrays in merge() patches as a documented no-op for both list and associative shapes.
  • Strip nested null values from newly appended list members during identity-based list merging, matching other write paths.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/wp-includes/class-wp-view-config-data.php Updates merge internals to guard against shape mismatches, define empty-array merge semantics, and strip nulls from appended list items.
tests/phpunit/tests/view-config-data.php Adds focused PHPUnit coverage for the three fixed defects (plus the mirror mismatch guard).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jorgefilipecosta jorgefilipecosta left a comment

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.

Pre-fix test results: each new test was run against trunk's class (source change reverted, tests kept) to confirm it fails without this PR. Per-assertion output inline.

1
);

$this->assertSame( $before, self::read_config( $data ) );

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

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.

Result of this assertion without the fix:

array(
	'view_list' => array(
		'published' => array( 'title' => 'Live' ),
	),
)

The default all view is gone, view_list became a slug-keyed map instead of a list, with no notice.

1
);

$this->assertSame( $before, self::read_config( $data ) );

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

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.

Result of this assertion without the fix:

array(
	'default_view' => array(
		'sort' => array( 'title', 'asc' ),
	),
)

The sort map (field/direction) was replaced by the bare list, with no notice.

1
);

$this->assertSame( $before, self::read_config( $data ) );

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

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.

Result of this assertion without the fix:

array(
	'default_view' => array(
		'filters' => array(
			array(
				'field'    => 'author',
				'operator' => 'isAny',
			),
		),
		'sort'    => array(),
	),
)

The same empty-array patch left filters untouched but emptied sort: one input, two different outcomes depending on the current shape.

1
);

$this->assertSame(

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

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.

Result of this assertion without the fix:

array(
	'view_list' => array(
		array(
			'slug'  => 'all',
			'title' => 'All items',
		),
		array(
			'slug' => 'mine',
			'view' => array( 'filters' => null ),
		),
	),
)

The appended member kept the literal 'filters' => null instead of dropping it the way every other write path does.

@github-actions

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.

@jorgefilipecosta jorgefilipecosta changed the title REST API: Fix silent data loss in view configuration merges View config: reject shape-mismatched merges, define empty-array semantics, strip nulls from appended members Jul 22, 2026
The $method parameter threaded through merge_properties() and
merge_list_by_identity() existed only to name the public method in the
_doing_it_wrong() notice. Report via __METHOD__ with a single
shape-agnostic message instead, keeping the notice actionable without
the extra plumbing.

See #65577.
Copilot AI review requested due to automatic review settings July 22, 2026 14:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/wp-includes/class-wp-view-config-data.php
Comment on lines +1728 to +1729
*
* @covers ::merge

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.

Suggested change
*
* @covers ::merge
* @ticket 65577
*
* @covers ::merge

Add ticket annotation in all new tests

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.

Fixed 👍

A non-empty list patch value applied with replace() bypassed the new
shape guard and silently swapped out an associative current value, while
the associative-over-list direction was already rejected. The guard now
runs before the replace() early return, covering both modes; an empty
array stays exempt so replace() with an empty list still clears a list.
Copilot AI review requested due to automatic review settings July 23, 2026 14:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/wp-includes/class-wp-view-config-data.php:501

  • The merge_properties() docblock says an empty array “merges nothing, so it is a no-op”, but merge_properties() is also used by replace() where an empty list can intentionally clear a list (because $replace_lists swaps lists wholesale). Consider clarifying this sentence to avoid implying the no-op rule applies to replace() as well.
	 * patch cannot silently destroy configuration. An empty array is
	 * shape-ambiguous and merges nothing, so it is a no-op: clearing a list is
	 * spelled replace() with an empty list, and resetting a key is spelled
	 * `null`.

@github-actions

Copy link
Copy Markdown

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 62833
GitHub commit: b0e62d8

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants