Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Refactor ProductQuery::merge_queries to accept multiple queries #7697

Merged
merged 18 commits into from
Nov 29, 2022

Conversation

dinhtungdu
Copy link
Member

@dinhtungdu dinhtungdu commented Nov 17, 2022

This PR refactors ProductQuery::merge_queries method to accept multiple arguments. This makes merge_queries the single source of trust in merging queries for Product Query blocks. This PR also improves the readability of the ProductQuery class.

More context: #7682 (comment)

Fixes #7745

Testing

Automated Tests

  • Changes in this PR are covered by Automated Tests.
    • Unit tests
    • E2E tests

User Facing Testing

Ensure products and order of products are the same as before refactoring.

  • Do not include in the Testing Notes

WooCommerce Visibility

  • WooCommerce Core
  • Feature plugin
  • Experimental

Performance Impact

N/a

@github-actions
Copy link
Contributor

The release ZIP for this PR is accessible via:

https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-7697.zip

@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2022

TypeScript Errors Report

Files with errors: 428
Total errors: 2062

🎉 🎉 This PR does not introduce new TS errors.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2022

Size Change: 0 B

Total Size: 970 kB

ℹ️ View Unchanged
Filename Size
build/active-filters-frontend.js 7.74 kB
build/active-filters-wrapper-frontend.js 6.01 kB
build/active-filters.js 7.32 kB
build/all-products-frontend.js 11.3 kB
build/all-products.js 33.1 kB
build/all-reviews.js 7.79 kB
build/attribute-filter-frontend.js 22.6 kB
build/attribute-filter-wrapper-frontend.js 7.14 kB
build/attribute-filter.js 12.3 kB
build/blocks-checkout.js 27.1 kB
build/cart-blocks/cart-accepted-payment-methods-frontend.js 1.38 kB
build/cart-blocks/cart-cross-sells-frontend.js 253 B
build/cart-blocks/cart-cross-sells-products--product-add-to-cart-frontend.js 5.48 kB
build/cart-blocks/cart-cross-sells-products-frontend.js 4.74 kB
build/cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend.js 5.04 kB
build/cart-blocks/cart-express-payment-frontend.js 779 B
build/cart-blocks/cart-items-frontend.js 298 B
build/cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend.js 5.29 kB
build/cart-blocks/cart-line-items-frontend.js 1.07 kB
build/cart-blocks/cart-order-summary-frontend.js 1.11 kB
build/cart-blocks/cart-totals-frontend.js 319 B
build/cart-blocks/empty-cart-frontend.js 346 B
build/cart-blocks/filled-cart-frontend.js 781 B
build/cart-blocks/order-summary-coupon-form-frontend.js 1.72 kB
build/cart-blocks/order-summary-discount-frontend.js 2.16 kB
build/cart-blocks/order-summary-fee-frontend.js 273 B
build/cart-blocks/order-summary-heading-frontend.js 455 B
build/cart-blocks/order-summary-shipping-frontend.js 5.99 kB
build/cart-blocks/order-summary-subtotal-frontend.js 274 B
build/cart-blocks/order-summary-taxes-frontend.js 435 B
build/cart-blocks/proceed-to-checkout-frontend.js 1.19 kB
build/cart-frontend.js 46.2 kB
build/cart.js 46.1 kB
build/checkout-blocks/actions-frontend.js 1.77 kB
build/checkout-blocks/billing-address--checkout-blocks/shipping-address-frontend.js 3.89 kB
build/checkout-blocks/billing-address-frontend.js 955 B
build/checkout-blocks/contact-information-frontend.js 1.8 kB
build/checkout-blocks/express-payment-frontend.js 1.13 kB
build/checkout-blocks/fields-frontend.js 344 B
build/checkout-blocks/order-note-frontend.js 1.14 kB
build/checkout-blocks/order-summary-cart-items-frontend.js 3.67 kB
build/checkout-blocks/order-summary-coupon-form-frontend.js 1.89 kB
build/checkout-blocks/order-summary-discount-frontend.js 2.28 kB
build/checkout-blocks/order-summary-fee-frontend.js 276 B
build/checkout-blocks/order-summary-frontend.js 1.11 kB
build/checkout-blocks/order-summary-shipping-frontend.js 6.04 kB
build/checkout-blocks/order-summary-subtotal-frontend.js 273 B
build/checkout-blocks/order-summary-taxes-frontend.js 436 B
build/checkout-blocks/payment-frontend.js 8.31 kB
build/checkout-blocks/shipping-address-frontend.js 1.07 kB
build/checkout-blocks/shipping-methods-frontend.js 5.01 kB
build/checkout-blocks/terms-frontend.js 1.64 kB
build/checkout-blocks/totals-frontend.js 324 B
build/checkout-frontend.js 48.3 kB
build/checkout.js 40.1 kB
build/featured-category.js 13.2 kB
build/featured-product.js 13.4 kB
build/filter-wrapper-frontend.js 13.8 kB
build/filter-wrapper.js 2.4 kB
build/general-style-rtl.css 1.29 kB
build/general-style.css 1.29 kB
build/handpicked-products.js 7.29 kB
build/legacy-template.js 2.85 kB
build/mini-cart-component-frontend.js 20 kB
build/mini-cart-contents-block/empty-cart-frontend.js 366 B
build/mini-cart-contents-block/filled-cart-frontend.js 230 B
build/mini-cart-contents-block/footer-frontend.js 2.95 kB
build/mini-cart-contents-block/items-frontend.js 237 B
build/mini-cart-contents-block/products-table-frontend.js 590 B
build/mini-cart-contents-block/shopping-button-frontend.js 289 B
build/mini-cart-contents-block/title-frontend.js 368 B
build/mini-cart-contents.js 17.1 kB
build/mini-cart-frontend.js 1.76 kB
build/mini-cart.js 4.28 kB
build/price-filter-frontend.js 13.6 kB
build/price-filter-wrapper-frontend.js 7.02 kB
build/price-filter.js 8.37 kB
build/price-format.js 1.19 kB
build/product-add-to-cart--product-button--product-category-list--product-image--product-price--product-r--a0326d00.js 227 B
build/product-add-to-cart--product-button--product-image--product-rating--product-title.js 151 B
build/product-add-to-cart-frontend.js 1.46 kB
build/product-add-to-cart.js 8.37 kB
build/product-best-sellers.js 7.62 kB
build/product-button--product-category-list--product-image--product-price--product-rating--product-sale-b--e17c7c01.js 432 B
build/product-button--product-image--product-rating--product-sale-badge--product-title.js 301 B
build/product-button-frontend.js 2.13 kB
build/product-button.js 3.82 kB
build/product-categories.js 2.36 kB
build/product-category-list-frontend.js 1.13 kB
build/product-category-list.js 502 B
build/product-category.js 8.62 kB
build/product-image-frontend.js 2.16 kB
build/product-image.js 3.93 kB
build/product-new.js 7.62 kB
build/product-on-sale.js 7.95 kB
build/product-price-frontend.js 2.15 kB
build/product-price.js 1.53 kB
build/product-query.js 3.27 kB
build/product-rating-frontend.js 1.44 kB
build/product-rating.js 787 B
build/product-sale-badge-frontend.js 1.38 kB
build/product-sale-badge.js 816 B
build/product-search.js 2.62 kB
build/product-sku-frontend.js 629 B
build/product-sku.js 377 B
build/product-stock-indicator-frontend.js 1.26 kB
build/product-stock-indicator.js 646 B
build/product-summary-frontend.js 1.52 kB
build/product-summary.js 921 B
build/product-tag-list-frontend.js 1.12 kB
build/product-tag-list.js 498 B
build/product-tag.js 8 kB
build/product-title-frontend.js 1.57 kB
build/product-title.js 3.3 kB
build/product-top-rated.js 7.87 kB
build/products-by-attribute.js 8.54 kB
build/rating-filter-frontend.js 7.16 kB
build/rating-filter-wrapper-frontend.js 5.41 kB
build/rating-filter.js 5.79 kB
build/reviews-by-category.js 11.2 kB
build/reviews-by-product.js 12.3 kB
build/reviews-frontend.js 7.02 kB
build/single-product-frontend.js 17.5 kB
build/single-product.js 10 kB
build/stock-filter-frontend.js 7.8 kB
build/stock-filter-wrapper-frontend.js 6.04 kB
build/stock-filter.js 6.71 kB
build/vendors--attribute-filter-wrapper--mini-cart-contents-block/footer--product-add-to-cart-frontend.js 6.86 kB
build/vendors--attribute-filter-wrapper-frontend.js 8.2 kB
build/vendors--cart-blocks/cart-cross-sells-products--cart-blocks/cart-line-items--cart-blocks/cart-order--671ca56f-frontend.js 5.26 kB
build/vendors--cart-blocks/cart-cross-sells-products--cart-blocks/order-summary-shipping--checkout-blocks--18f9376a-frontend.js 19.1 kB
build/vendors--cart-blocks/cart-cross-sells-products--product-add-to-cart-frontend.js 7.54 kB
build/vendors--cart-blocks/cart-line-items--checkout-blocks/order-summary-cart-items--mini-cart-contents---233ab542-frontend.js 3.14 kB
build/vendors--cart-blocks/order-summary-shipping--checkout-blocks/billing-address--checkout-blocks/order--5b8feb0b-frontend.js 4.85 kB
build/vendors--cart-blocks/order-summary-shipping--checkout-blocks/order-summary-shipping--checkout-block--dda5866c-frontend.js 8.85 kB
build/wc-blocks-data.js 18.7 kB
build/wc-blocks-editor-style-rtl.css 5.19 kB
build/wc-blocks-editor-style.css 5.19 kB
build/wc-blocks-google-analytics.js 1.56 kB
build/wc-blocks-middleware.js 934 B
build/wc-blocks-registry.js 2.92 kB
build/wc-blocks-shared-context.js 1.52 kB
build/wc-blocks-shared-hocs.js 1.73 kB
build/wc-blocks-style-rtl.css 24.2 kB
build/wc-blocks-style.css 24.1 kB
build/wc-blocks-vendors-style-rtl.css 1.95 kB
build/wc-blocks-vendors-style.css 1.95 kB
build/wc-blocks-vendors.js 62.4 kB
build/wc-blocks.js 2.63 kB
build/wc-payment-method-bacs.js 816 B
build/wc-payment-method-cheque.js 811 B
build/wc-payment-method-cod.js 909 B
build/wc-payment-method-paypal.js 837 B
build/wc-settings.js 2.6 kB

compressed-size-action

@dinhtungdu dinhtungdu changed the title Refactor ProductQuery::mereg_queries to accept multiple queries Refactor ProductQuery::merge_queries to accept multiple queries Nov 18, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Nov 24, 2022

Script Dependencies Report

There is no changed script dependency between this branch and trunk.

This comment was automatically generated by the ./github/compare-assets action.

@dinhtungdu dinhtungdu self-assigned this Nov 24, 2022
@dinhtungdu dinhtungdu added status: needs review type: refactor The issue/PR is related to refactoring. block-type: product-query Issues related to/affecting all product-query variations. labels Nov 24, 2022
@dinhtungdu dinhtungdu marked this pull request as ready for review November 24, 2022 09:48
@rubikuserbot rubikuserbot requested a review from a team November 24, 2022 09:49
@dinhtungdu dinhtungdu added the skip-changelog PRs that you don't want to appear in the changelog. label Nov 24, 2022
Copy link
Contributor

@gigitux gigitux left a comment

Choose a reason for hiding this comment

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

Thanks a lot for this refactor! It improved a lot the readability of the code! 💪

I did an observation, but it isn't a blocker. Feel free to merge it!

'suppress_filters',
'tax_query',
)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it is premature optimization, but we recreate $valid_query_vars array for each query. Isn't it better to move the creation of $valid_query_vars outside this function?

Copy link
Member Author

@dinhtungdu dinhtungdu Nov 25, 2022

Choose a reason for hiding this comment

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

Thank you for the head-up. I addressed this in 0a3bb43. To me it's more readability than performance improvement as we don't process more than two level depth arrays at the moment, and we still need to recreate the array each time the class is initialized. But it makes the code easier to read so it makes sense to me to implement the optimization.

@dinhtungdu
Copy link
Member Author

@sunyatasattva Can I have your review on this PR as well, as you're working on Adding support for filtering by attributes within the block?

Comment on lines 191 to 194
if ( empty( array_intersect( $this->get_valid_query_vars(), array_keys( $query ) ) ) ) {
return $this->merge_queries( $acc, ...array_values( $query ) );
}
return $this->array_merge_recursive_replace_non_array_properties( $acc, $query );
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you mind explaining a little bit more about this logic?

  1. What do you mean we “unpack it”?
  2. What are the use-cases for non valid query keys? Are they our custom __woocommerce queries?
  3. Why do we recursively replace them?

I'd just like to understand the entire flow a bit more and why these things are needed (and perhaps it could be useful to document them in the PHPDocs for posterity).

Copy link
Member Author

@dinhtungdu dinhtungdu Nov 28, 2022

Choose a reason for hiding this comment

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

  1. What do you mean we “unpack it”?

It's restructuring. I used unpack as PHP uses that word. I updated the comment to include both (unpack/destructure).

  1. Why do we recursively replace them?

We recursively replace only non-array values. array_merge_recursive(), which I used initially, will merge the value with the same key, but it's not what we want. For example:

$base = ['orderby' => 'date']
$new  = ['orderby' => 'meta_value_num']

With array_merge_recursive(), the result will be: ['orderby' => ['date', 'meta_value_num']]

But we want this: ['orderby' => 'meta_value_num']
  1. What are the use-cases for non valid query keys? Are they our custom __woocommerce queries?

Explaining this makes me think I might be overthinking.

Are they our custom __woocommerce queries?

No. Query keys (or query vars) here are WP_Query variables. Using valid here is misleading, it should be built-in instead. I check if the current array contains the built-in query vars to deal with unknown incoming arrays. For example:

If we feed merge_queries with an array like:

[
	[
		'meta_query' => [
			...<meta_queries_here>
		]
	]
]

merge_queries will restructure the array until it gets an array containing the meta_query key, which is a built-in query variable, then starts merging.

But, as this is a private method, which is only used inside the ProductQuery class, so we can control the inputs of merge_queries calls, I think this logic can be simplified by destructuring until we get the associative arrays, new commit is coming...

Copy link
Member Author

@dinhtungdu dinhtungdu Nov 28, 2022

Choose a reason for hiding this comment

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

But, as this is a private method, which is only used inside the ProductQuery class, so we can control the inputs of merge_queries calls, I think this logic can be simplified by destructuring until we get the associative arrays, new commit is coming...

We can't do this because we're returning associative arrays, for example get_queries_by_attributes() or get_queries_by_applied_filters().

	private function get_queries_by_applied_filters() {
		return array(
			'price_filter'        => $this->get_filter_by_price_query(),
			'attributes_filter'   => $this->get_filter_by_attributes_query(),
			'stock_status_filter' => $this->get_filter_by_stock_status_query(),
		);
	}

We can check and unpack/destructure array contains those keys (price_filter, attributes_filter, stock_status_filter), but we will have to update the logic every time we add more queries.

Copy link
Member Author

Choose a reason for hiding this comment

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

After considering, I reverted e6df617. I think the benefit of a versatile merge_queries() method outperforms the complexity checking for valid query variables.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks a lot @dinhtungdu for the amazing explanation and in-depth commentary! This was extremely helpful to understand this somewhat convoluted logic. And I agree that simplifying is better in this case: glad that some rubberducking was also useful to you.

Just a small note:

What do you mean we “unpack it”?

It's restructuring. I used unpack as PHP uses that word. I updated the comment to include both (unpack/destructure).

By this you mean: ...array_values( $query ) right? In which case, I guess we call this “unpacking” “spreading” and not “destructuring” in the JS world:

Destructuring is used to create varibles from array items or object properties. Spread syntax is used to unpack iterables such as arrays, objects, and function calls.

Just in case you want to update your comment. But it's not a big deal. Thanks a lot for unpacking ( 🤭 ) the code for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

By this you mean: ...array_values( $query ) right? In which case, I guess we call this “unpacking” “spreading” and not “destructuring” in the JS world:

Just in case you want to update your comment. But it's not a big deal. Thanks a lot for unpacking ( 🤭 ) the code for me.

I usually misuse those terms 🤦🏽. I updated the code. Thanks so much, it's easier to memorize for me now 🥰

@sunyatasattva
Copy link
Contributor

Thanks @dinhtungdu, it looks really good to me. I just have a clarification question, but otherwise the code seems very solid and I like this API more! 👏

Instead of checking if array contains built-in query variables, we only
check if the array contains string keys as we can control the inputs of
merge_queries.
Copy link
Contributor

@gigitux gigitux left a comment

Choose a reason for hiding this comment

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

Thanks for providing a great documentation! LGTM!

@dinhtungdu dinhtungdu merged commit 1f587b6 into trunk Nov 29, 2022
@dinhtungdu dinhtungdu deleted the refactor/merge_queries branch November 29, 2022 03:13
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
block-type: product-query Issues related to/affecting all product-query variations. skip-changelog PRs that you don't want to appear in the changelog. type: refactor The issue/PR is related to refactoring.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Product Query: Unify data structure for tax_query and meta_query
3 participants