Cache order year_months in options for performance. (#50066)
* Cache order year_months in options for performance. * Modify to prevent unnecessary option changed. Add unit tests. * Add the strict type directive. * Use exact check to use more cache instances. * Add clean state test. * Add namespace. * Namespace fixes.
This commit is contained in:
parent
5922b42577
commit
f0b637f9c4
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: performance
|
||||
|
||||
Cache order dates in options for performance.
|
|
@ -216,7 +216,7 @@ class ListTable extends WP_List_Table {
|
|||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function set_items_per_page( $default, string $option, int $value ) {
|
||||
public function set_items_per_page( $default, string $option, int $value ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound -- backwards compat.
|
||||
return 'edit_' . $this->order_type . '_per_page' === $option ? absint( $value ) : $default;
|
||||
}
|
||||
|
||||
|
@ -754,6 +754,8 @@ class ListTable extends WP_List_Table {
|
|||
* @return void
|
||||
*/
|
||||
private function months_filter() {
|
||||
global $wp_locale;
|
||||
|
||||
// XXX: [review] we may prefer to move this logic outside of the ListTable class.
|
||||
|
||||
/**
|
||||
|
@ -767,29 +769,12 @@ class ListTable extends WP_List_Table {
|
|||
return;
|
||||
}
|
||||
|
||||
global $wp_locale;
|
||||
global $wpdb;
|
||||
|
||||
$orders_table = esc_sql( OrdersTableDataStore::get_orders_table_name() );
|
||||
$utc_offset = wc_timezone_offset();
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$order_dates = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT DISTINCT YEAR( t.date_created_local ) AS year,
|
||||
MONTH( t.date_created_local ) AS month
|
||||
FROM ( SELECT DATE_ADD( date_created_gmt, INTERVAL $utc_offset SECOND ) AS date_created_local FROM $orders_table WHERE type = %s AND status != 'trash' ) t
|
||||
ORDER BY year DESC, month DESC
|
||||
",
|
||||
$this->order_type
|
||||
)
|
||||
);
|
||||
|
||||
$m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
|
||||
echo '<select name="m" id="filter-by-date">';
|
||||
echo '<option ' . selected( $m, 0, false ) . ' value="0">' . esc_html__( 'All dates', 'woocommerce' ) . '</option>';
|
||||
|
||||
$order_dates = $this->get_and_maybe_update_months_filter_cache();
|
||||
|
||||
foreach ( $order_dates as $date ) {
|
||||
$month = zeroise( $date->month, 2 );
|
||||
$month_year_text = sprintf(
|
||||
|
@ -810,6 +795,64 @@ class ListTable extends WP_List_Table {
|
|||
echo '</select>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order year-months cache. We cache the results in the options table, since these results will change very infrequently.
|
||||
* We use the heuristic to always return current year-month when getting from cache to prevent an additional query.
|
||||
*
|
||||
* @return array List of year-months.
|
||||
*/
|
||||
protected function get_and_maybe_update_months_filter_cache(): array {
|
||||
global $wpdb;
|
||||
|
||||
// We cache in the options table since it's won't be invalidated soon.
|
||||
$cache_option_value_name = 'wc_' . $this->order_type . '_list_table_months_filter_cache_value';
|
||||
$cache_option_date_name = 'wc_' . $this->order_type . '_list_table_months_filter_cache_date';
|
||||
|
||||
$cached_timestamp = get_option( $cache_option_date_name, 0 );
|
||||
|
||||
// new day, new cache.
|
||||
if ( 0 === $cached_timestamp || gmdate( 'j', time() ) !== gmdate( 'j', $cached_timestamp ) || ( time() - $cached_timestamp ) > 60 * 60 * 24 ) {
|
||||
$cached_value = false;
|
||||
} else {
|
||||
$cached_value = get_option( $cache_option_value_name );
|
||||
}
|
||||
|
||||
if ( false !== $cached_value ) {
|
||||
// Always add current year month for cache stability. This allows us to not hydrate the cache on every order update.
|
||||
$current_year_month = new \stdClass();
|
||||
$current_year_month->year = gmdate( 'Y', time() );
|
||||
$current_year_month->month = gmdate( 'n', time() );
|
||||
if ( count( $cached_value ) === 0 || (
|
||||
$cached_value[0]->year !== $current_year_month->year ||
|
||||
$cached_value[0]->month !== $current_year_month->month )
|
||||
) {
|
||||
array_unshift( $cached_value, $current_year_month );
|
||||
}
|
||||
return $cached_value;
|
||||
}
|
||||
|
||||
$orders_table = esc_sql( OrdersTableDataStore::get_orders_table_name() );
|
||||
$utc_offset = wc_timezone_offset();
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$order_dates = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT DISTINCT YEAR( t.date_created_local ) AS year,
|
||||
MONTH( t.date_created_local ) AS month
|
||||
FROM ( SELECT DATE_ADD( date_created_gmt, INTERVAL $utc_offset SECOND ) AS date_created_local FROM $orders_table WHERE type = %s AND status != 'trash' ) t
|
||||
ORDER BY year DESC, month DESC
|
||||
",
|
||||
$this->order_type
|
||||
)
|
||||
);
|
||||
|
||||
update_option( $cache_option_date_name, time() );
|
||||
update_option( $cache_option_value_name, $order_dates );
|
||||
|
||||
return $order_dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the customer filter dropdown.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
declare( strict_types = 1);
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable;
|
||||
use Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait;
|
||||
|
||||
/**
|
||||
* Tests related to order list table in admin.
|
||||
*/
|
||||
class ListTableTest extends \WC_Unit_Test_Case {
|
||||
use HPOSToggleTrait;
|
||||
|
||||
/**
|
||||
* @var ListTable
|
||||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* Setup - enables HPOS.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->setup_cot();
|
||||
$this->toggle_cot_authoritative( true );
|
||||
$this->sut = new ListTable();
|
||||
$set_order_type = function ( $order_type ) {
|
||||
$this->order_type = $order_type;
|
||||
};
|
||||
$set_order_type->call( $this->sut, 'shop_order' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to call protected get_and_maybe_update_months_filter_cache.
|
||||
*
|
||||
* @param ListTable $sut ListTable instance.
|
||||
*
|
||||
* @return array YearMonth Array.
|
||||
*/
|
||||
public function call_get_and_maybe_update_months_filter_cache( ListTable $sut ) {
|
||||
$callable = function () {
|
||||
return $this->get_and_maybe_update_months_filter_cache();
|
||||
};
|
||||
return $callable->call( $sut );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Test that current month is returned even there's no order.
|
||||
*/
|
||||
public function test_get_and_maybe_update_months_filter_cache_always_return_current() {
|
||||
$year_months = $this->call_get_and_maybe_update_months_filter_cache( $this->sut );
|
||||
$this->assertEmpty( $year_months );
|
||||
$year_months = $this->call_get_and_maybe_update_months_filter_cache( $this->sut ); // when loaded from cache, we always return current year month.
|
||||
$this->assertEquals( $year_months[0]->year, gmdate( 'Y', time() ) );
|
||||
$this->assertEquals( $year_months[0]->month, gmdate( 'n', time() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Test that current month is returned.
|
||||
*/
|
||||
public function test_get_and_maybe_update_months_filter_cache_always_return_current_with_order() {
|
||||
\WC_Helper_Order::create_order();
|
||||
$year_months = $this->call_get_and_maybe_update_months_filter_cache( $this->sut );
|
||||
$this->assertEquals( $year_months[0]->year, gmdate( 'Y', time() ) );
|
||||
$this->assertEquals( $year_months[0]->month, gmdate( 'n', time() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Test that backfilled order is recognized.
|
||||
*/
|
||||
public function test_get_and_maybe_update_months_filter_cache_always_backfilled() {
|
||||
$order = \WC_Helper_Order::create_order();
|
||||
$order->set_date_created( new \WC_DateTime( '1991-01-01 00:00:00' ) );
|
||||
$order->save();
|
||||
|
||||
$year_months = $this->call_get_and_maybe_update_months_filter_cache( $this->sut );
|
||||
$this->assertEquals( end( $year_months )->year, 1991 );
|
||||
$this->assertEquals( end( $year_months )->month, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Test that reading from cache works as expected.
|
||||
*/
|
||||
public function test_get_and_maybe_update_months_filter_cache_always_return_current_and_backfilled() {
|
||||
$order = \WC_Helper_Order::create_order();
|
||||
$order->set_date_created( new \WC_DateTime( '1991-01-01 00:00:00' ) );
|
||||
$order->save();
|
||||
|
||||
\WC_Helper_Order::create_order();
|
||||
|
||||
$year_months = $this->call_get_and_maybe_update_months_filter_cache( $this->sut );
|
||||
$this->assertEquals( $year_months[0]->year, gmdate( 'Y', time() ) );
|
||||
$this->assertEquals( $year_months[0]->month, gmdate( 'n', time() ) );
|
||||
$this->assertEquals( end( $year_months )->year, 1991 );
|
||||
$this->assertEquals( end( $year_months )->month, 1 );
|
||||
|
||||
// Loading from cache doesn't alter the behavior.
|
||||
|
||||
$year_months = $this->call_get_and_maybe_update_months_filter_cache( $this->sut );
|
||||
$this->assertEquals( $year_months[0]->year, gmdate( 'Y', time() ) );
|
||||
$this->assertEquals( $year_months[0]->month, gmdate( 'n', time() ) );
|
||||
$this->assertEquals( end( $year_months )->year, 1991 );
|
||||
$this->assertEquals( end( $year_months )->month, 1 );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue