Revert "Revert "[COT] Add the orders cache" (#34992)"
This reverts commit 0b5d7ab9ad
.
This commit is contained in:
parent
a531f83b6d
commit
248e8ffa81
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add a cache for orders, to use when custom order tables are enabled
|
|
@ -10,9 +10,11 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
use Automattic\WooCommerce\Utilities\NumberUtil;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -203,6 +205,11 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
|
|||
|
||||
$this->save_items();
|
||||
|
||||
if ( OrderUtil::orders_cache_usage_is_enabled() ) {
|
||||
$order_cache = wc_get_container()->get( OrderCache::class );
|
||||
$order_cache->update_if_cached( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger action after saving to the DB.
|
||||
*
|
||||
|
|
|
@ -488,6 +488,8 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
|
|||
$visibility_class[] = 'show_options_if_checked';
|
||||
}
|
||||
|
||||
$must_disable = ArrayUtil::get_value_or_default( $value, 'disabled', false );
|
||||
|
||||
if ( ! isset( $value['checkboxgroup'] ) || 'start' === $value['checkboxgroup'] ) {
|
||||
$has_tooltip = isset( $value['tooltip'] ) && '' !== $value['tooltip'];
|
||||
$tooltip_container_class = $has_tooltip ? 'with-tooltip' : '';
|
||||
|
@ -515,6 +517,7 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
|
|||
?>
|
||||
<label for="<?php echo esc_attr( $value['id'] ); ?>">
|
||||
<input
|
||||
<?php echo $must_disable ? 'disabled' : ''; ?>
|
||||
name="<?php echo esc_attr( $value['field_name'] ); ?>"
|
||||
id="<?php echo esc_attr( $value['id'] ); ?>"
|
||||
type="checkbox"
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
@ -30,13 +33,26 @@ class WC_Order_Factory {
|
|||
return false;
|
||||
}
|
||||
|
||||
$use_orders_cache = OrderUtil::orders_cache_usage_is_enabled();
|
||||
if ( $use_orders_cache ) {
|
||||
$order_cache = wc_get_container()->get( OrderCache::class );
|
||||
$order = $order_cache->get( $order_id );
|
||||
if ( ! is_null( $order ) ) {
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
|
||||
$classname = self::get_class_name_for_order_id( $order_id );
|
||||
if ( ! $classname ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return new $classname( $order_id );
|
||||
$order = new $classname( $order_id );
|
||||
if ( $use_orders_cache && $order instanceof \WC_Abstract_Legacy_Order ) {
|
||||
$order_cache->set( $order, $order_id );
|
||||
}
|
||||
return $order;
|
||||
} catch ( Exception $e ) {
|
||||
wc_caught_exception( $e, __FUNCTION__, array( $order_id ) );
|
||||
return false;
|
||||
|
@ -56,6 +72,22 @@ class WC_Order_Factory {
|
|||
$result = array();
|
||||
$order_ids = array_filter( array_map( array( __CLASS__, 'get_order_id' ), $order_ids ) );
|
||||
|
||||
$already_cached_orders = array();
|
||||
$use_orders_cache = OrderUtil::orders_cache_usage_is_enabled();
|
||||
if ( $use_orders_cache ) {
|
||||
$uncached_order_ids = array();
|
||||
$order_cache = wc_get_container()->get( OrderCache::class );
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
$cached_order = $order_cache->get( absint( $order_id ) );
|
||||
if ( is_null( $cached_order ) ) {
|
||||
$uncached_order_ids[] = $order_id;
|
||||
} else {
|
||||
$already_cached_orders[] = $cached_order;
|
||||
}
|
||||
}
|
||||
$order_ids = $uncached_order_ids;
|
||||
}
|
||||
|
||||
// We separate order list by class, since their datastore might be different.
|
||||
$order_list_by_class = array();
|
||||
$order_id_classnames = self::get_class_names_for_order_ids( $order_ids );
|
||||
|
@ -99,9 +131,16 @@ class WC_Order_Factory {
|
|||
}
|
||||
|
||||
// restore the sort order.
|
||||
$result = array_replace( array_flip( $order_ids ), $result );
|
||||
$result = array_values( array_replace( array_flip( $order_ids ), $result ) );
|
||||
|
||||
return array_values( $result );
|
||||
if ( $use_orders_cache ) {
|
||||
foreach ( $result as $order ) {
|
||||
$order_cache->set( $order );
|
||||
}
|
||||
return array_merge( $already_cached_orders, $result );
|
||||
} else {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Caches;
|
||||
|
||||
use Automattic\WooCommerce\Caching\ObjectCache;
|
||||
|
||||
/**
|
||||
* A class to cache order objects.
|
||||
*/
|
||||
class OrderCache extends ObjectCache {
|
||||
|
||||
/**
|
||||
* Get the identifier for the type of the cached objects.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_object_type(): string {
|
||||
return 'order';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of an object to be cached.
|
||||
*
|
||||
* @param array|object $object The object to be cached.
|
||||
* @return int|string|null The id of the object, or null if it can't be determined.
|
||||
*/
|
||||
protected function get_object_id( $object ) {
|
||||
return $object->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an object before caching it.
|
||||
*
|
||||
* @param array|object $object The object to validate.
|
||||
* @return string[]|null An array of error messages, or null if the object is valid.
|
||||
*/
|
||||
protected function validate( $object ): ?array {
|
||||
if ( ! $object instanceof \WC_Abstract_Legacy_Order ) {
|
||||
return array( 'The supplied order is not an instance of WC_Order, ' . gettype( $object ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Caches;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* A class to control the usage of the orders cache.
|
||||
*/
|
||||
class OrderCacheController {
|
||||
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
const FEATURE_NAME = 'orders_cache';
|
||||
|
||||
/**
|
||||
* The orders cache to use.
|
||||
*
|
||||
* @var OrderCache
|
||||
*/
|
||||
private $order_cache;
|
||||
|
||||
/**
|
||||
* The orders cache to use.
|
||||
*
|
||||
* @var FeaturesController
|
||||
*/
|
||||
private $features_controller;
|
||||
|
||||
/**
|
||||
* The backup value of the cache usage enable status, stored while the cache is temporarily disabled.
|
||||
*
|
||||
* @var null|bool
|
||||
*/
|
||||
private $orders_cache_usage_backup = null;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::add_action( FeaturesController::FEATURE_ENABLED_CHANGED_ACTION, array( $this, 'handle_feature_enable_changed' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Class initialization, invoked by the DI container.
|
||||
*
|
||||
* @internal
|
||||
* @param OrderCache $order_cache The order cache engine to use.
|
||||
* @param FeaturesController $features_controller The features controller to use.
|
||||
*/
|
||||
final public function init( OrderCache $order_cache, FeaturesController $features_controller ) {
|
||||
$this->order_cache = $order_cache;
|
||||
$this->features_controller = $features_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the feature enable changed action, when the orders cache is enabled or disabled it flushes it.
|
||||
*
|
||||
* @param string $feature_id The id of the feature whose enable status changed.
|
||||
* @param bool $enabled Whether the feature has been enabled or disabled.
|
||||
* @return void
|
||||
*/
|
||||
private function handle_feature_enable_changed( string $feature_id, bool $enabled ): void {
|
||||
if ( self::FEATURE_NAME === $feature_id ) {
|
||||
$this->order_cache->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the order cache usage setting.
|
||||
*
|
||||
* @param bool $enable True if the order cache should be used, false if not.
|
||||
* @throws \Exception Attempt to enable the orders cache usage while it's temporarily disabled.
|
||||
*/
|
||||
public function set_orders_cache_usage( bool $enable ): void {
|
||||
$this->features_controller->change_feature_enable( $enable );
|
||||
$this->orders_cache_usage_backup = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the order cache usage setting.
|
||||
*
|
||||
* @return bool True if order cache usage setting is currently enabled, false if not.
|
||||
*/
|
||||
public function orders_cache_usage_is_enabled(): bool {
|
||||
return ! $this->orders_cache_usage_is_temporarly_disabled() && $this->features_controller->feature_is_enabled( self::FEATURE_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily disable the order cache if it's enabled.
|
||||
*
|
||||
* This is a purely in-memory operation: a variable is created with the value
|
||||
* of the current enable status for the feature, and this variable
|
||||
* is checked by orders_cache_usage_is_enabled. In the next request the
|
||||
* feature will be again enabled or not depending on how the feature is set.
|
||||
*/
|
||||
public function temporarily_disable_orders_cache_usage(): void {
|
||||
if ( $this->orders_cache_usage_is_temporarly_disabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->orders_cache_usage_backup = $this->orders_cache_usage_is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the order cache has been temporarily disabled.
|
||||
*
|
||||
* @return bool True if the order cache is currently temporarily disabled.
|
||||
*/
|
||||
public function orders_cache_usage_is_temporarly_disabled(): bool {
|
||||
return null !== $this->orders_cache_usage_backup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the order cache usage that had been temporarily disabled.
|
||||
*/
|
||||
public function maybe_restore_orders_cache_usage(): void {
|
||||
$this->orders_cache_usage_backup = null;
|
||||
}
|
||||
}
|
|
@ -164,13 +164,13 @@ abstract class ObjectCache {
|
|||
/**
|
||||
* Add an object to the cache, or update an already cached object.
|
||||
*
|
||||
* @param int|string|null $id Id of the object to be cached, if null, get_object_id will be used to get it.
|
||||
* @param object|array $object The object to be cached.
|
||||
* @param int|string|null $id Id of the object to be cached, if null, get_object_id will be used to get it.
|
||||
* @param int $expiration Expiration of the cached data in seconds from the current time, or DEFAULT_EXPIRATION to use the default value.
|
||||
* @return bool True on success, false on error.
|
||||
* @throws CacheException Invalid parameter, or null id was passed and get_object_id returns null too.
|
||||
*/
|
||||
public function set( $id = null, $object, int $expiration = self::DEFAULT_EXPIRATION ): bool {
|
||||
public function set( $object, $id = null, int $expiration = self::DEFAULT_EXPIRATION ): bool {
|
||||
if ( null === $object ) {
|
||||
throw new CacheException( "Can't cache a null value", $this, $id );
|
||||
}
|
||||
|
@ -185,20 +185,22 @@ abstract class ObjectCache {
|
|||
|
||||
$this->verify_expiration_value( $expiration );
|
||||
|
||||
if ( null === $id ) {
|
||||
$id = $this->get_object_id( $object );
|
||||
if ( null === $id ) {
|
||||
throw new CacheException( "Null id supplied and the cache class doesn't implement get_object_id", $this );
|
||||
$errors = $this->validate( $object );
|
||||
if ( ! is_null( $errors ) ) {
|
||||
try {
|
||||
$id = $this->get_id_from_object_if_null( $object, $id );
|
||||
} catch ( \Throwable $ex ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||
// Nothing else to do, we won't be able to add any significant object id to the CacheException and that's it.
|
||||
}
|
||||
|
||||
if ( count( $errors ) === 1 ) {
|
||||
throw new CacheException( 'Object validation/serialization failed: ' . $errors[0], $this, $id, $errors );
|
||||
} elseif ( ! empty( $errors ) ) {
|
||||
throw new CacheException( 'Object validation/serialization failed', $this, $id, $errors );
|
||||
}
|
||||
}
|
||||
|
||||
$errors = $this->validate( $object );
|
||||
if ( null !== $errors && 1 === count( $errors ) ) {
|
||||
throw new CacheException( 'Object validation/serialization failed: ' . $errors[0], $this, $id, $errors );
|
||||
} elseif ( ! empty( $errors ) ) {
|
||||
throw new CacheException( 'Object validation/serialization failed', $this, $id, $errors );
|
||||
}
|
||||
|
||||
$id = $this->get_id_from_object_if_null( $object, $id );
|
||||
$data = $this->serialize( $object );
|
||||
|
||||
/**
|
||||
|
@ -213,7 +215,49 @@ abstract class ObjectCache {
|
|||
$data = apply_filters( "woocommerce_after_serializing_{$this->object_type}_for_caching", $data, $object, $id );
|
||||
|
||||
$this->last_cached_data = $data;
|
||||
return $this->get_cache_engine()->cache_object( $this->get_cache_key_prefix() . $id, $data, self::DEFAULT_EXPIRATION === $expiration ? $this->default_expiration : $expiration );
|
||||
return $this->get_cache_engine()->cache_object(
|
||||
$this->get_cache_key_prefix() . $id,
|
||||
$data,
|
||||
self::DEFAULT_EXPIRATION === $expiration ? $this->default_expiration : $expiration
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an object in the cache, but only if an object is already cached with the same id.
|
||||
*
|
||||
* @param object|array $object The new object that will replace the already cached one.
|
||||
* @param int|string|null $id Id of the object to be cached, if null, get_object_id will be used to get it.
|
||||
* @param int $expiration Expiration of the cached data in seconds from the current time, or DEFAULT_EXPIRATION to use the default value.
|
||||
* @return bool True on success, false on error or if no object wiith the supplied id was cached.
|
||||
* @throws CacheException Invalid parameter, or null id was passed and get_object_id returns null too.
|
||||
*/
|
||||
public function update_if_cached( $object, $id = null, int $expiration = self::DEFAULT_EXPIRATION ): bool {
|
||||
$id = $this->get_id_from_object_if_null( $object, $id );
|
||||
|
||||
if ( ! $this->is_cached( $id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->set( $object, $id, $expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id from an object if the id itself is null.
|
||||
*
|
||||
* @param object|array $object The object to get the id from.
|
||||
* @param int|string|null $id An object id or null.
|
||||
* @return int|string|null Passed $id if it wasn't null, otherwise id obtained from $object using get_object_id.
|
||||
* @throws CacheException Passed $id is null and get_object_id returned null too.
|
||||
*/
|
||||
private function get_id_from_object_if_null( $object, $id ) {
|
||||
if ( null === $id ) {
|
||||
$id = $this->get_object_id( $object );
|
||||
if ( null === $id ) {
|
||||
throw new CacheException( "Null id supplied and the cache class doesn't implement get_object_id", $this );
|
||||
}
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,7 +305,7 @@ abstract class ObjectCache {
|
|||
return null;
|
||||
}
|
||||
|
||||
$this->set( $id, $object, $expiration );
|
||||
$this->set( $object, $id, $expiration );
|
||||
$data = $this->last_cached_data;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Caches\OrderCacheController;
|
||||
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -81,6 +84,20 @@ class CustomOrdersTableController {
|
|||
*/
|
||||
private $features_controller;
|
||||
|
||||
/**
|
||||
* The orders cache object to use.
|
||||
*
|
||||
* @var OrderCache
|
||||
*/
|
||||
private $order_cache;
|
||||
|
||||
/**
|
||||
* The orders cache controller object to use.
|
||||
*
|
||||
* @var OrderCacheController
|
||||
*/
|
||||
private $order_cache_controller;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*/
|
||||
|
@ -114,18 +131,24 @@ class CustomOrdersTableController {
|
|||
* @param OrdersTableRefundDataStore $refund_data_store The refund data store to use.
|
||||
* @param BatchProcessingController $batch_processing_controller The batch processing controller to use.
|
||||
* @param FeaturesController $features_controller The features controller instance to use.
|
||||
* @param OrderCache $order_cache The order cache engine to use.
|
||||
* @param OrderCacheController $order_cache_controller The order cache controller to use.
|
||||
*/
|
||||
final public function init(
|
||||
OrdersTableDataStore $data_store,
|
||||
DataSynchronizer $data_synchronizer,
|
||||
OrdersTableRefundDataStore $refund_data_store,
|
||||
BatchProcessingController $batch_processing_controller,
|
||||
FeaturesController $features_controller ) {
|
||||
FeaturesController $features_controller,
|
||||
OrderCache $order_cache,
|
||||
OrderCacheController $order_cache_controller ) {
|
||||
$this->data_store = $data_store;
|
||||
$this->data_synchronizer = $data_synchronizer;
|
||||
$this->batch_processing_controller = $batch_processing_controller;
|
||||
$this->refund_data_store = $refund_data_store;
|
||||
$this->features_controller = $features_controller;
|
||||
$this->order_cache = $order_cache;
|
||||
$this->order_cache_controller = $order_cache_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -466,8 +489,14 @@ class CustomOrdersTableController {
|
|||
return $value;
|
||||
}
|
||||
|
||||
if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option || $value === $old_value || false === $old_value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->order_cache->flush();
|
||||
|
||||
/**
|
||||
* Commenting out for better testability.
|
||||
* TODO: Re-enable the following code once the COT to posts table sync is implemented (it's currently commented out to ease testing).
|
||||
$sync_is_pending = 0 !== $this->data_synchronizer->get_current_orders_pending_sync_count();
|
||||
if ( $sync_is_pending ) {
|
||||
throw new \Exception( "The authoritative table for orders storage can't be changed while there are orders out of sync" );
|
||||
|
@ -575,4 +604,15 @@ class CustomOrdersTableController {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the order deleted/trashed hooks.
|
||||
*
|
||||
* @param int $order_id The id of the order deleted or trashed.
|
||||
*/
|
||||
private function after_order_deleted_or_trashed( int $order_id ): void {
|
||||
if ( $this->features_controller->feature_is_enabled( OrderCacheController::FEATURE_NAME ) ) {
|
||||
$this->order_cache->remove( $order_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Caches\OrderCacheController;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessorInterface;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
|
@ -111,6 +113,7 @@ class DataSynchronizer implements BatchProcessorInterface {
|
|||
* Delete the custom orders database tables.
|
||||
*/
|
||||
public function delete_database_tables() {
|
||||
$this->cache->flush();
|
||||
$table_names = $this->data_store->get_all_table_names();
|
||||
|
||||
foreach ( $table_names as $table_name ) {
|
||||
|
@ -329,6 +332,8 @@ WHERE
|
|||
* @param array $batch Batch details.
|
||||
*/
|
||||
public function process_batch( array $batch ) : void {
|
||||
$this->cache_controller->temporarily_disable_orders_cache_usage();
|
||||
|
||||
if ( $this->custom_orders_table_is_authoritative() ) {
|
||||
foreach ( $batch as $id ) {
|
||||
$order = wc_get_order( $id );
|
||||
|
@ -344,6 +349,7 @@ WHERE
|
|||
}
|
||||
if ( 0 === $this->get_total_pending_count() ) {
|
||||
$this->cleanup_synchronization_state();
|
||||
$this->cache_controller->maybe_restore_orders_cache_usage();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
|||
|
||||
use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\CLIRunner;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
|
@ -25,7 +24,6 @@ class COTMigrationServiceProvider extends AbstractServiceProvider {
|
|||
protected $provides = array(
|
||||
PostsToOrdersMigrationController::class,
|
||||
CLIRunner::class,
|
||||
DataSynchronizer::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -37,5 +35,6 @@ class COTMigrationServiceProvider extends AbstractServiceProvider {
|
|||
*/
|
||||
public function register() {
|
||||
$this->share( PostsToOrdersMigrationController::class );
|
||||
$this->share( CLIRunner::class );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Caches\OrderCacheController;
|
||||
use Automattic\WooCommerce\Caching\TransientsEngine;
|
||||
use Automattic\WooCommerce\DataBase\Migrations\CustomOrderTable\CLIRunner;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController;
|
||||
|
@ -36,6 +39,8 @@ class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
|
|||
CLIRunner::class,
|
||||
OrdersTableDataStoreMeta::class,
|
||||
OrdersTableRefundDataStore::class,
|
||||
OrderCache::class,
|
||||
OrderCacheController::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -54,6 +59,8 @@ class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
|
|||
OrdersTableRefundDataStore::class,
|
||||
BatchProcessingController::class,
|
||||
FeaturesController::class,
|
||||
OrderCache::class,
|
||||
OrderCacheController::class,
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -111,6 +111,11 @@ class FeaturesController {
|
|||
'description' => __( 'Enable the high performance order storage feature.', 'woocommerce' ),
|
||||
'is_experimental' => true,
|
||||
),
|
||||
OrderCacheController::FEATURE_NAME => array(
|
||||
'name' => __( 'Orders cache', 'woocommerce' ),
|
||||
'description' => __( 'Enable the usage of a cache for shop orders', 'woocommerce' ),
|
||||
'is_experimental' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$this->legacy_feature_ids = array( 'analytics', 'new_navigation', 'new_product_management' );
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Utilities;
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCacheController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Utilities\COTMigrationUtil;
|
||||
use WC_Order;
|
||||
use WP_Post;
|
||||
|
@ -35,6 +37,15 @@ final class OrderUtil {
|
|||
return wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get whether the orders cache should be used or not.
|
||||
*
|
||||
* @return bool True if the orders cache should be used, false otherwise.
|
||||
*/
|
||||
public static function orders_cache_usage_is_enabled() : bool {
|
||||
return wc_get_container()->get( OrderCacheController::class )->orders_cache_usage_is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if posts and order custom table sync is enabled and there are no pending orders.
|
||||
*
|
||||
|
|
|
@ -230,8 +230,9 @@ class WC_REST_Orders_Controller_Tests extends WC_REST_Unit_Test_Case {
|
|||
$order = new \WC_Order();
|
||||
$order->set_status( 'completed' );
|
||||
$order->save();
|
||||
$order_id = $order->get_id();
|
||||
|
||||
$request = new \WP_REST_Request( 'DELETE', '/wc/v3/orders/' . $order->get_id() );
|
||||
$request = new \WP_REST_Request( 'DELETE', '/wc/v3/orders/' . $order_id );
|
||||
$response = $this->server->dispatch( $request );
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
|
@ -239,13 +240,13 @@ class WC_REST_Orders_Controller_Tests extends WC_REST_Unit_Test_Case {
|
|||
// Check that the response includes order data from the order (before deletion).
|
||||
$data = $response->get_data();
|
||||
$this->assertArrayHasKey( 'id', $data );
|
||||
$this->assertEquals( $data['id'], $order->get_id() );
|
||||
$this->assertEquals( $data['id'], $order_id );
|
||||
$this->assertEquals( 'completed', $data['status'] );
|
||||
|
||||
wp_cache_flush();
|
||||
|
||||
// Check the order was actually deleted.
|
||||
$order = wc_get_order( $order->get_id() );
|
||||
$order = wc_get_order( $order_id );
|
||||
$this->assertEquals( 'trash', $order->get_status( 'edit' ) );
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
$this->expectException( CacheException::class );
|
||||
$this->expectExceptionMessage( "Can't cache a null value" );
|
||||
|
||||
$this->sut->set( 'the_id', null );
|
||||
$this->sut->set( null );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
$this->expectException( CacheException::class );
|
||||
$this->expectExceptionMessage( "Can't cache a non-object, non-array value" );
|
||||
|
||||
$this->sut->set( 'the_id', 1234 );
|
||||
$this->sut->set( 1234 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,7 +114,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
$this->expectException( CacheException::class );
|
||||
$this->expectExceptionMessage( "Object id must be an int, a string, or null for 'set'" );
|
||||
|
||||
$this->sut->set( array( 1, 2 ), array( 'foo' ) );
|
||||
$this->sut->set( array( 'foo' ), array( 1, 2 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,7 +130,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
$this->expectException( CacheException::class );
|
||||
$this->expectExceptionMessage( 'Invalid expiration value, must be ObjectCache::DEFAULT_EXPIRATION or a value between 1 and ObjectCache::MAX_EXPIRATION' );
|
||||
|
||||
$this->sut->set( 'the_id', array( 'foo' ), $expiration );
|
||||
$this->sut->set( array( 'foo' ), 'the_id', $expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,7 +139,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
public function try_set_when_cache_engine_fails() {
|
||||
$this->cache_engine->caching_succeeds = false;
|
||||
|
||||
$result = $this->sut->set( 'the_id', array( 'foo' ) );
|
||||
$result = $this->sut->set( array( 'foo' ), 'the_id' );
|
||||
$this->assertFalse( $result );
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_set_new_object_with_id_caches_with_expected_key() {
|
||||
$object = array( 'foo' );
|
||||
$result = $this->sut->set( 'the_id', $object );
|
||||
$result = $this->sut->set( $object, 'the_id' );
|
||||
|
||||
$this->assertTrue( $result );
|
||||
|
||||
|
@ -165,9 +165,9 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_setting_two_objects_result_in_same_prefix() {
|
||||
$object_1 = array( 'foo' );
|
||||
$this->sut->set( 'the_id_1', $object_1 );
|
||||
$this->sut->set( $object_1, 'the_id_1' );
|
||||
$object_2 = array( 1, 2, 3, 4 );
|
||||
$this->sut->set( 9999, $object_2 );
|
||||
$this->sut->set( $object_2, 9999 );
|
||||
|
||||
$prefix = 'woocommerce_object_cache|the_type|random_1|';
|
||||
|
||||
|
@ -183,7 +183,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
* @testdox 'set' uses the default expiration value if no explicit value is passed.
|
||||
*/
|
||||
public function test_set_with_default_expiration() {
|
||||
$this->sut->set( 'the_id', array( 'foo' ) );
|
||||
$this->sut->set( array( 'foo' ), 'the_id' );
|
||||
$this->assertEquals( $this->sut->get_default_expiration_value(), $this->cache_engine->last_expiration );
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
* @testdox 'set' uses the explicitly passed expiration value.
|
||||
*/
|
||||
public function test_set_with_explicit_expiration() {
|
||||
$this->sut->set( 'the_id', array( 'foo' ), 1234 );
|
||||
$this->sut->set( array( 'foo' ), 'the_id', 1234 );
|
||||
$this->assertEquals( 1234, $this->cache_engine->last_expiration );
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
$this->expectException( CacheException::class );
|
||||
$this->expectExceptionMessage( "Null id supplied and the cache class doesn't implement get_object_id" );
|
||||
|
||||
$this->sut->set( null, array( 'foo' ) );
|
||||
$this->sut->set( array( 'foo' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,11 +227,88 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$sut->set( null, array( 'id' => 1234 ) );
|
||||
$sut->set( array( 'id' => 1234 ) );
|
||||
|
||||
$this->assertEquals( 'woocommerce_object_cache|the_type|random|1235', array_keys( $this->cache_engine->cache )[0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'update_if_cached' does nothing if no object is cached with the passed (or obtained) id.
|
||||
*
|
||||
* @testWith [1234]
|
||||
* [null]
|
||||
*
|
||||
* @param ?int $id Id to pass to update_if_cached.
|
||||
*/
|
||||
public function test_update_if_cached_does_nothing_for_not_cached_id( ?int $id ) {
|
||||
// phpcs:disable Squiz.Commenting
|
||||
|
||||
$sut = new class() extends ObjectCache {
|
||||
public function get_object_type(): string {
|
||||
return 'the_type';
|
||||
}
|
||||
|
||||
protected function get_object_id( $object ) {
|
||||
return $object['id'];
|
||||
}
|
||||
};
|
||||
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$result = $sut->update_if_cached( array( 'id' => 1234 ), $id );
|
||||
$this->assertFalse( $result );
|
||||
$this->assertEmpty( $this->cache_engine->cache );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'update_if_cached' updates an already cached object the same way as 'set'.
|
||||
*
|
||||
* @testWith [1234]
|
||||
* [null]
|
||||
*
|
||||
* @param ?int $id Id to pass to update_if_cached.
|
||||
*/
|
||||
public function test_update_if_cached_updates_already_cached_object( ?int $id ) {
|
||||
// phpcs:disable Squiz.Commenting
|
||||
|
||||
$sut = new class() extends ObjectCache {
|
||||
public function get_object_type(): string {
|
||||
return 'the_type';
|
||||
}
|
||||
|
||||
protected function get_object_id( $object ) {
|
||||
return $object['id'];
|
||||
}
|
||||
|
||||
protected function get_random_string(): string {
|
||||
return 'random';
|
||||
}
|
||||
};
|
||||
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$sut->set( array( 'id' => 1234 ), $id );
|
||||
$this->assertEquals( 'woocommerce_object_cache|the_type|random|1234', array_keys( $this->cache_engine->cache )[0] );
|
||||
|
||||
$new_value = array(
|
||||
'id' => 1234,
|
||||
'foo' => 'bar',
|
||||
);
|
||||
$result = $sut->update_if_cached( $new_value, $id );
|
||||
$this->assertTrue( $result );
|
||||
$this->assertEquals( $this->cache_engine->cache['woocommerce_object_cache|the_type|random|1234'], array( 'data' => $new_value ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'update_if_cached' throws an exception if no object id is passed and the class doesn't implement 'get_object_id'.
|
||||
*/
|
||||
public function test_update_if_cached_null_id_without_id_retrieval_implementation() {
|
||||
$this->expectException( CacheException::class );
|
||||
$this->expectExceptionMessage( "Null id supplied and the cache class doesn't implement get_object_id" );
|
||||
|
||||
$this->sut->update_if_cached( array( 'foo' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'set' caches the value returned by 'serialize'.
|
||||
*/
|
||||
|
@ -252,7 +329,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$sut->set( 1234, $object );
|
||||
$sut->set( $object, 1234 );
|
||||
|
||||
$cached = array_values( $this->cache_engine->cache )[0];
|
||||
$expected = array( 'the_data' => $object );
|
||||
|
@ -269,7 +346,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_set_with_custom_serialization_that_returns_errors( int $errors_count ) {
|
||||
$exception = null;
|
||||
$errors = 1 === $errors_count ? array( 'Foo failed' ) : array( 'Foo failed', 'Bar failed' );
|
||||
$errors = $errors_count === 1 ? array( 'Foo failed' ) : array( 'Foo failed', 'Bar failed' );
|
||||
$object = array( 'foo' );
|
||||
|
||||
// phpcs:disable Squiz.Commenting
|
||||
|
@ -293,13 +370,13 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
try {
|
||||
$sut->set( 1234, $object );
|
||||
$sut->set( $object, 1234 );
|
||||
} catch ( CacheException $thrown ) {
|
||||
$exception = $thrown;
|
||||
}
|
||||
|
||||
$expected_message = 'Object validation/serialization failed';
|
||||
if ( 1 === $errors_count ) {
|
||||
if ( $errors_count === 1 ) {
|
||||
$expected_message .= ': Foo failed';
|
||||
}
|
||||
$this->assertEquals( $expected_message, $exception->getMessage() );
|
||||
|
@ -343,7 +420,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_try_getting_previously_cached_object() {
|
||||
$object = array( 'foo' );
|
||||
$this->sut->set( 'the_id', $object );
|
||||
$this->sut->set( $object, 'the_id' );
|
||||
|
||||
$result = $this->sut->get( 'the_id' );
|
||||
$this->assertEquals( $object, $result );
|
||||
|
@ -465,7 +542,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$sut->set( 'the_id', array( 1, 2 ) );
|
||||
$sut->set( array( 1, 2 ), 'the_id' );
|
||||
|
||||
$result = $sut->get( 'the_id' );
|
||||
$expected = array( 1, 2, 3 );
|
||||
|
@ -476,8 +553,8 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
* @testdox 'remove' removes a cached object and returns true, or returns false if there's no cached object under the passed id.
|
||||
*/
|
||||
public function test_remove() {
|
||||
$this->sut->set( 'the_id_1', array( 'foo' ) );
|
||||
$this->sut->set( 'the_id_2', array( 'bar' ) );
|
||||
$this->sut->set( array( 'foo' ), 'the_id_1' );
|
||||
$this->sut->set( array( 'bar' ), 'the_id_2' );
|
||||
|
||||
$result_1 = $this->sut->remove( 'the_id_1' );
|
||||
$result_2 = $this->sut->remove( 'the_id_X' );
|
||||
|
@ -493,12 +570,12 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
* @testdox 'flush' deletes the stored cache key prefix, effectively rendering the cached objects inaccessible.
|
||||
*/
|
||||
public function test_flush() {
|
||||
$this->sut->set( 'the_id', array( 'foo' ) );
|
||||
$this->sut->set( array( 'foo' ), 'the_id' );
|
||||
|
||||
$this->sut->flush();
|
||||
$this->assertFalse( get_option( 'wp_object_cache_key_prefix_the_type' ) );
|
||||
|
||||
$this->sut->set( 'the_id_2', array( 'bar' ) );
|
||||
$this->sut->set( array( 'bar' ), 'the_id_2' );
|
||||
|
||||
$expected_new_prefix = 'woocommerce_object_cache|the_type|random_2|';
|
||||
$this->assertEquals( $expected_new_prefix, get_option( 'wp_object_cache_key_prefix_the_type' ) );
|
||||
|
@ -534,7 +611,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$object = array( 'foo' );
|
||||
$sut->set( 'the_id', $object );
|
||||
$sut->set( $object, 'the_id' );
|
||||
|
||||
$expected_cached = array( 'data' => $object );
|
||||
$this->assertEquals( $expected_cached, array_values( $engine->cache )[0] );
|
||||
|
@ -560,7 +637,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
);
|
||||
|
||||
$object = array( 'foo' );
|
||||
$this->sut->set( 'the_id', $object );
|
||||
$this->sut->set( $object, 'the_id' );
|
||||
|
||||
$expected_cached = array( 'data' => $object );
|
||||
$this->assertEquals( $expected_cached, array_values( $engine->cache )[0] );
|
||||
|
@ -590,7 +667,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
);
|
||||
|
||||
$object = array( 'fizz' );
|
||||
$this->sut->set( 'the_id', $object );
|
||||
$this->sut->set( $object, 'the_id' );
|
||||
|
||||
$expected_cached = array(
|
||||
'data' => $object,
|
||||
|
@ -626,7 +703,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
3
|
||||
);
|
||||
|
||||
$this->sut->set( 'the_id', $original_object );
|
||||
$this->sut->set( $original_object, 'the_id' );
|
||||
$retrieved_object = $this->sut->get( 'the_id' );
|
||||
|
||||
$this->assertEquals( $replacement_object, $retrieved_object );
|
||||
|
@ -658,7 +735,7 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
|
|||
2
|
||||
);
|
||||
|
||||
$this->sut->set( 'the_id', array( 'foo' ) );
|
||||
$this->sut->set( array( 'foo' ), 'the_id' );
|
||||
$this->sut->remove( $operation_succeeds ? 'the_id' : 'INVALID_ID' );
|
||||
|
||||
$this->assertEquals( $operation_succeeds ? 'the_id' : 'INVALID_ID', $id_passed_to_action );
|
||||
|
|
Loading…
Reference in New Issue