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
Merged
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 73 additions & 54 deletions src/BlockTypes/ProductQuery.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use WP_Query;

// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_tax_query
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
Expand Down Expand Up @@ -132,24 +134,11 @@ public function build_query( $query ) {
'tax_query' => array(),
);

$queries_by_attributes = $this->get_queries_by_attributes( $parsed_block );
$queries_by_filters = $this->get_queries_by_applied_filters();
$orderby_query = $this->get_custom_orderby_query( $query['orderby'] );

$base_query = array_merge(
return $this->merge_queries(
$common_query_values,
$orderby_query
);

return array_reduce(
array_merge(
$queries_by_attributes,
$queries_by_filters
),
function( $acc, $query ) {
return $this->merge_queries( $acc, $query );
},
$base_query
$this->get_custom_orderby_query( $query['orderby'] ),
$this->get_queries_by_attributes( $parsed_block ),
$this->get_queries_by_applied_filters()
);
}

Expand All @@ -160,21 +149,16 @@ function( $acc, $query ) {
* @return array
*/
private function get_products_ids_by_attributes( $parsed_block ) {
$queries_by_attributes = $this->get_queries_by_attributes( $parsed_block );

$query = array_reduce(
$queries_by_attributes,
function( $acc, $query ) {
return $this->merge_queries( $acc, $query );
},
$query = $this->merge_queries(
array(
'post_type' => 'product',
'post__in' => array(),
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(),
'tax_query' => array(),
)
),
$this->get_queries_by_attributes( $parsed_block )
);

$products = new \WP_Query( $query );
Expand All @@ -186,16 +170,68 @@ function( $acc, $query ) {
/**
* Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter.
*
* @param array $a The first query.
* @param array $b The second query.
* @param array[] ...$queries Query arrays to be merged.
* @return array
*/
private function merge_queries( $a, $b ) {
$a['post__in'] = ( isset( $b['post__in'] ) && ! empty( $b['post__in'] ) ) ? $this->intersect_arrays_when_not_empty( $a['post__in'], $b['post__in'] ) : $a['post__in'];
$a['meta_query'] = ( isset( $b['meta_query'] ) && ! empty( $b['meta_query'] ) ) ? array_merge( $a['meta_query'], array( $b['meta_query'] ) ) : $a['meta_query'];
$a['tax_query'] = ( isset( $b['tax_query'] ) && ! empty( $b['tax_query'] ) ) ? array_merge( $a['tax_query'], array( $b['tax_query'] ) ) : $a['tax_query'];
private function merge_queries( ...$queries ) {
$valid_query_vars = array_keys( ( new WP_Query() )->fill_query_vars( array() ) );
$valid_query_vars = array_merge(
$valid_query_vars,
// fill_query_vars doesn't include these vars so we need to add them manually.
array(
'date_query',
'exact',
'ignore_sticky_posts',
'lazy_load_term_meta',
'meta_compare_key',
'meta_compare',
'meta_query',
'meta_type_key',
'meta_type',
'nopaging',
'offset',
'order',
'orderby',
'page',
'post_type',
'posts_per_page',
'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.


$merged_query = array_reduce(
$queries,
function( $acc, $query ) use ( $valid_query_vars ) {
if ( ! is_array( $query ) ) {
return $acc;
}
if ( empty( array_intersect( $valid_query_vars, array_keys( $query ) ) ) ) {
return $this->merge_queries( $acc, ...array_values( $query ) );
}
return array_merge_recursive( $acc, $query );
},
array()
);

/**
* If there are duplicated items in post__in, it means that we need to
* use the intersection of the results, which in this case, are the
* duplicated items.
*/
if (
! empty( $merged_query['post__in'] ) &&
count( $merged_query['post__in'] ) > count( array_unique( $merged_query['post__in'] ) )
) {
$merged_query['post__in'] = array_unique(
array_diff(
$merged_query['post__in'],
array_unique( $merged_query['post__in'] )
)
);
}

return $a;
return $merged_query;
}

/**
Expand Down Expand Up @@ -258,9 +294,11 @@ private function get_custom_orderby_query( $orderby ) {
private function get_stock_status_query( $stock_statii ) {
return array(
'meta_query' => array(
'key' => '_stock_status',
'value' => (array) $stock_statii,
'compare' => 'IN',
array(
'key' => '_stock_status',
'value' => (array) $stock_statii,
'compare' => 'IN',
),
),
);
}
Expand Down Expand Up @@ -492,23 +530,4 @@ function( $stock_status ) {
);
}

/**
* Intersect arrays neither of them are empty, otherwise merge them.
*
* @param array ...$arrays Arrays.
* @return array
*/
private function intersect_arrays_when_not_empty( ...$arrays ) {
return array_reduce(
$arrays,
function( $acc, $array ) {
if ( ! empty( $array ) && ! empty( $acc ) ) {
return array_intersect( $acc, $array );
}
return array_merge( $acc, $array );
},
array()
);
}

}