Make the db utils class non-static, change the custom orders tables UI

- Rename DBUtils to DatabaseUtils, and register it in the DI container
  (instead of having it just contain static methods)

- The tool for the custom orders tables has now two shapes:
  "create tables" and "delete tables" (so no regeneration, migration...)

- Not yet used constants and methods from DataSynchronizer removed

- Added missing method comments
This commit is contained in:
Nestor Soriano 2022-02-09 11:58:19 +01:00
parent fc0c45a204
commit 3964a2255a
No known key found for this signature in database
GPG Key ID: 08110F3518C12CAD
9 changed files with 238 additions and 189 deletions

View File

@ -7,6 +7,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper as WCConnectionHelper;
use Automattic\WooCommerce\Utilities\DBUtil;
@ -345,7 +346,9 @@ class WC_Install {
self::create_tables();
}
$missing_tables = DBUtil::verify_database_tables_exist( self::get_schema() );
$missing_tables = wc_get_container()
->get( DatabaseUtil::class )
->get_missing_tables( self::get_schema() );
if ( 0 < count( $missing_tables ) ) {
if ( $modify_notice ) {

View File

@ -12,6 +12,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\Orders
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProductAttributesLookupServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\RestockRefundedItemsAdjusterServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\UtilsClassesServiceProvider;
/**
* PSR11 compliant dependency injection container for WooCommerce.
@ -43,6 +44,7 @@ final class Container implements \Psr\Container\ContainerInterface {
ProductAttributesLookupServiceProvider::class,
ProxiesServiceProvider::class,
RestockRefundedItemsAdjusterServiceProvider::class,
UtilsClassesServiceProvider::class,
);
/**

View File

@ -8,7 +8,7 @@ namespace Automattic\WooCommerce\Internal\DataStores\Orders;
defined( 'ABSPATH' ) || exit;
/**
* This is the main class that controls the custom orders table feature. Its responsibilities are:
* This is the main class that controls the custom orders tables feature. Its responsibilities are:
*
* - Allowing to enable and disable the feature while it's in development (show_feature method)
* - Displaying UI components (entries in the tools page and in settings)
@ -19,7 +19,7 @@ defined( 'ABSPATH' ) || exit;
class CustomOrdersTableController {
/**
* The name of the option for enabling the usage of the custom orders table
* The name of the option for enabling the usage of the custom orders tables
*/
const CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION = 'woocommerce_custom_orders_table_enabled';
@ -146,7 +146,7 @@ class CustomOrdersTableController {
* @return WC_Object_Data_Store_Interface|string The actual data store to use.
*/
private function get_data_store_instance( $default_data_store ) {
if ( $this->is_feature_visible() && $this->custom_orders_table_usage_is_enabled() && ! $this->data_synchronizer->data_regeneration_is_in_progress() ) {
if ( $this->is_feature_visible() && $this->custom_orders_table_usage_is_enabled() ) {
return $this->data_store;
} else {
return $default_data_store;
@ -165,61 +165,32 @@ class CustomOrdersTableController {
return $tools_array;
}
$orders_table_exists = $this->data_synchronizer->check_orders_table_exists();
$generation_is_in_progress = $this->data_synchronizer->data_regeneration_is_in_progress();
if ( $orders_table_exists ) {
$generate_item_name = __( 'Regenerate the custom orders table', 'woocommerce' );
$generate_item_desc = __( 'This tool will regenerate the custom orders table data from existing orders data from the posts table. This process may take a while.', 'woocommerce' );
$generate_item_return = __( 'Custom orders table is being regenerated', 'woocommerce' );
$generate_item_button = __( 'Regenerate', 'woocommerce' );
} else {
$generate_item_name = __( 'Create and fill custom orders table', 'woocommerce' );
$generate_item_desc = __( 'This tool will create the custom orders table and fill it with existing orders data from the posts table. This process may take a while.', 'woocommerce' );
$generate_item_return = __( 'Custom orders table is being filled', 'woocommerce' );
$generate_item_button = __( 'Create', 'woocommerce' );
}
$entry = array(
'name' => $generate_item_name,
'desc' => $generate_item_desc,
'requires_refresh' => true,
'callback' => function() use ( $generate_item_return ) {
$this->initiate_regeneration_from_tools_page();
return $generate_item_return;
},
);
if ( $generation_is_in_progress ) {
$entry['button'] = sprintf(
/* translators: %d: How many orders have been processed so far. */
__( 'Filling in progress (%d)', 'woocommerce' ),
$this->data_synchronizer->get_regeneration_processed_orders_count()
);
$entry['disabled'] = true;
} else {
$entry['button'] = $generate_item_button;
}
$tools_array['regenerate_custom_orders_table'] = $entry;
if ( $orders_table_exists ) {
// Delete the table.
if ( $this->data_synchronizer->check_orders_table_exists() ) {
$tools_array['delete_custom_orders_table'] = array(
'name' => __( 'Delete the custom orders table', 'woocommerce' ),
'name' => __( 'Delete the custom orders tables', 'woocommerce' ),
'desc' => sprintf(
'<strong class="red">%1$s</strong> %2$s',
__( 'Note:', 'woocommerce' ),
__( 'This will delete the custom orders table. You can create it again with the "Create and fill custom orders table" tool.', 'woocommerce' )
__( 'This will delete the custom orders tables. The tables can be deleted only if they are not not in use (via Settings > Advanced > Custom data stores). You can create them again at any time with the "Create the custom orders tables" tool.', 'woocommerce' )
),
'button' => __( 'Delete', 'woocommerce' ),
'requires_refresh' => true,
'callback' => function () {
$this->delete_custom_orders_table();
return __( 'Custom orders table has been deleted.', 'woocommerce' );
$this->delete_custom_orders_tables();
return __( 'Custom orders tables have been deleted.', 'woocommerce' );
},
'button' => __( 'Delete', 'woocommerce' ),
'disabled' => $this->custom_orders_table_usage_is_enabled(),
);
} else {
$tools_array['create_custom_orders_table'] = array(
'name' => __( 'Create the custom orders tables', 'woocommerce' ),
'desc' => __( 'This tool will create the custom orders tables. Once created you can go to WooCommerce > Settings > Advanced > Custom data stores and configure the usage of the tables.', 'woocommerce' ),
'requires_refresh' => true,
'callback' => function() {
$this->create_custom_orders_tables();
return __( 'Custom orders tables have been created. You can now go to WooCommerce > Settings > Advanced > Custom data stores.', 'woocommerce' );
},
'button' => __( 'Create', 'woocommerce' ),
);
}
@ -227,41 +198,35 @@ class CustomOrdersTableController {
}
/**
* Initiate the custom orders table (re)generation in response to the user pressing the tool button.
* Create the custom orders tables in response to the user pressing the tool button.
*
* @throws \Exception Can't initiate regeneration.
* @throws \Exception Can't create the tables.
*/
private function initiate_regeneration_from_tools_page() {
private function create_custom_orders_tables() {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
if ( ! isset( $_REQUEST['_wpnonce'] ) || false === wp_verify_nonce( $_REQUEST['_wpnonce'], 'debug_action' ) ) {
throw new \Exception( 'Invalid nonce' );
}
$this->check_can_do_table_regeneration();
$this->data_synchronizer->initiate_regeneration();
}
/**
* Can the custom orders table regeneration be started?
*
* @throws \Exception The table regeneration can't be started.
*/
private function check_can_do_table_regeneration() {
if ( ! $this->is_feature_visible() ) {
throw new \Exception( "Can't do custom orders table regeneration: the feature isn't enabled" );
throw new \Exception( "Can't create the custom orders tables: the feature isn't enabled" );
}
if ( $this->data_synchronizer->data_regeneration_is_in_progress() ) {
throw new \Exception( "Can't do custom orders table regeneration: regeneration is already in progress" );
}
$this->data_synchronizer->create_database_tables();
}
/**
* Delete the custom orders table and any related options and data.
* Delete the custom orders tables and any related options and data in response to the user pressing the tool button.
*
* @throws \Exception Can't delete the tables.
*/
private function delete_custom_orders_table() {
private function delete_custom_orders_tables() {
if ( $this->custom_orders_table_usage_is_enabled() ) {
throw new \Exception( "Can't delete the custom orders tables: they are currently in use (via Settings > Advanced > Custom data stores)." );
}
delete_option( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION );
$this->data_synchronizer->delete_custom_orders_table();
$this->data_synchronizer->delete_database_tables();
}
/**
@ -282,7 +247,7 @@ class CustomOrdersTableController {
/**
* Get the settings for the "Custom data stores" section in the "Advanced" tab,
* with entries for managing the custom orders table if appropriate.
* with entries for managing the custom orders tables if appropriate.
*
* @param array $settings The original settings array.
* @param string $section_id The settings section to get the settings for.
@ -294,25 +259,30 @@ class CustomOrdersTableController {
}
$title_item = array(
'title' => __( 'Custom orders table', 'woocommerce' ),
'title' => __( 'Custom orders tables', 'woocommerce' ),
'type' => 'title',
);
if ( ! $this->data_synchronizer->check_orders_table_exists() ) {
$title_item['desc'] = __( 'Generate custom tables first by going to WooCommerce > Status > Tools > Create and fill custom orders table', 'woocommerce' );
}
if ( $this->data_synchronizer->check_orders_table_exists() ) {
$settings[] = $title_item;
$settings[] = array(
'title' => __( 'Enable table usage', 'woocommerce' ),
'desc' => __( 'Use the custom orders table as the main orders data store.', 'woocommerce' ),
'title' => __( 'Enable tables usage', 'woocommerce' ),
'desc' => __( 'Use the custom orders tables as the main orders data store.', 'woocommerce' ),
'id' => self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION,
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'start',
);
} else {
$title_item['desc'] = sprintf(
/* translators: %1$ = <em> tag, %2$ = </em> tag. */
__( 'Create the tables first by going to %1$sWooCommerce > Status > Tools%2$s and running %1$sCreate the custom orders tables%2$s.', 'woocommerce' ),
'<em>',
'</em>'
);
$settings[] = $title_item;
}
$settings[] = array( 'type' => 'sectionend' );

View File

@ -5,21 +5,18 @@
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\WooCommerce\Utilities\DBUtil;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
defined( 'ABSPATH' ) || exit;
/**
* This class handles the data migration/synchronization for the custom orders table. Its responsibilites are:
* This class handles the database structure creation and the data synchronization for the custom orders tables. Its responsibilites are:
*
* - Performing the initial table creation and filling (triggered by initiate_regeneration)
* - Synchronizing changes between the custom orders table and the posts table whenever changes in orders happen.
* - Providing entry points for creating and deleting the required database tables.
* - Synchronizing changes between the custom orders tables and the posts table whenever changes in orders happen.
*/
class DataSynchronizer {
const CUSTOM_ORDERS_TABLE_DATA_REGENERATION_IN_PROGRESS = 'woocommerce_custom_orders_table_data_regeneration_in_progress';
const CUSTOM_ORDERS_TABLE_DATA_REGENERATION_DONE_COUNT = 'woocommerce_custom_orders_table_data_regeneration_done_count';
/**
* The data store object to use.
*
@ -27,6 +24,13 @@ class DataSynchronizer {
*/
private $data_store;
/**
* The database util object to use.
*
* @var DatabaseUtil
*/
private $database_util;
// TODO: Add a constructor to handle hooks as appropriate.
/**
@ -34,55 +38,41 @@ class DataSynchronizer {
*
* @internal
* @param OrdersTableDataStore $data_store The data store to use.
* @param DatabaseUtil $database_util The database util class to use.
*/
final public function init( OrdersTableDataStore $data_store ) {
final public function init( OrdersTableDataStore $data_store, DatabaseUtil $database_util ) {
$this->data_store = $data_store;
$this->database_util = $database_util;
}
/**
* Does the custom orders table exist in the database?
* Does the custom orders tables exist in the database?
*
* @return bool True if the custom orders table exist in the database.
* @return bool True if the custom orders tables exist in the database.
*/
public function check_orders_table_exists(): bool {
$missing_tables = DBUtil::verify_database_tables_exist( $this->data_store->get_schema() );
if ( count( $missing_tables ) > 0 ) {
delete_option( self::CUSTOM_ORDERS_TABLE_DATA_REGENERATION_IN_PROGRESS );
}
$missing_tables = $this->database_util->get_missing_tables( $this->data_store->get_database_schema() );
return count( $missing_tables ) === 0;
}
/**
* Is a table regeneration in progress?
*
* @return bool True if a table regeneration in currently progress
* Create the custom orders database tables.
*/
public function data_regeneration_is_in_progress(): bool {
return 'yes' === get_option( self::CUSTOM_ORDERS_TABLE_DATA_REGENERATION_IN_PROGRESS );
public function create_database_tables() {
$this->database_util->dbdelta( $this->data_store->get_database_schema() );
}
/**
* Initiate a table regeneration process.
* Delete the custom orders database tables.
*/
public function initiate_regeneration() {
update_option( self::CUSTOM_ORDERS_TABLE_DATA_REGENERATION_IN_PROGRESS, 'yes' );
update_option( self::CUSTOM_ORDERS_TABLE_DATA_REGENERATION_DONE_COUNT, 0 );
DBUtil::create_database_tables( $this->data_store->get_schema() );
public function delete_database_tables() {
$table_names = $this->data_store->get_all_table_names();
foreach ( $table_names as $table_name ) {
$this->database_util->drop_database_table( $table_name );
}
}
/**
* How many orders have been processed as part of the custom orders table regeneration?
*
* @return int Number of orders already processed, 0 if no regeneration is in progress.
*/
public function get_regeneration_processed_orders_count(): int {
return (int)get_option( self::CUSTOM_ORDERS_TABLE_DATA_REGENERATION_DONE_COUNT, 0 );
}
/**
* Delete the custom orders table and the associated information.
*/
public function delete_custom_orders_table() {
// TODO: Delete the tables and any associated data (e.g. options).
}
}

View File

@ -5,8 +5,6 @@
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\Jetpack\Constants;
defined( 'ABSPATH' ) || exit;
/**
@ -24,19 +22,42 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
return $wpdb->prefix . 'wc_orders';
}
/**
* Get the order addresses table name.
*
* @return string The order addresses table name.
*/
public function get_addresses_table_name() {
global $wpdb;
return $wpdb->prefix . 'wc_order_addresses';
}
/**
* Get the orders operational data table name.
*
* @return string The orders operational data table name.
*/
public function get_operational_data_table_name() {
global $wpdb;
return $wpdb->prefix . 'wc_order_operational_data';
}
/**
* Get the names of all the tables involved in the custom orders table feature.
*
* @return string[]
*/
public function get_all_table_names() {
return array(
$this->get_orders_table_name(),
$this->get_addresses_table_name(),
$this->get_operational_data_table_name(),
);
}
// TODO: Add methods for other table names as appropriate.
//phpcs:disable Squiz.Commenting.FunctionComment.Missing
//phpcs:disable Squiz.Commenting, Generic.Commenting
public function get_total_refunded( $order ) {
// TODO: Implement get_total_refunded() method.
@ -153,14 +174,19 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
return 'line_item';
}
//phpcs:enable Squiz.Commenting.FunctionComment.Missing
//phpcs:enable Squiz.Commenting, Generic.Commenting
public function get_schema() {
/**
* Get the SQL needed to create all the tables needed for the custom orders table feature.
*
* @return string
*/
public function get_database_schema() {
$orders_table_name = $this->get_orders_table_name();
$addresses_table_name = $this->get_addresses_table_name();
$operational_data_table_name = $this->get_operational_data_table_name();
$table = "
$sql = "
CREATE TABLE $orders_table_name (
id bigint(20) unsigned auto_increment,
post_id bigint(20) unsigned null,
@ -222,6 +248,6 @@ CREATE TABLE $operational_data_table_name (
KEY order_id (order_id),
KEY order_key (order_key)
);";
return $table;
return $sql;
}
}

View File

@ -1,6 +1,6 @@
<?php
/**
* ProductAttributesLookupServiceProvider class file.
* OrdersDataStoreServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
@ -9,9 +9,10 @@ use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
/**
* Service provider for the ProductAttributesLookupServiceProvider namespace.
* Service provider for the classes in the Internal\DataStores\Orders namespace.
*/
class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
@ -30,7 +31,7 @@ class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
* Register the classes.
*/
public function register() {
$this->share( DataSynchronizer::class )->addArgument( OrdersTableDataStore::class );
$this->share( DataSynchronizer::class )->addArguments( array( OrdersTableDataStore::class, DatabaseUtil::class ) );
$this->share( CustomOrdersTableController::class )->addArguments( array( OrdersTableDataStore::class, DataSynchronizer::class ) );
$this->share( OrdersTableDataStore::class );
}

View File

@ -0,0 +1,31 @@
<?php
/**
* UtilsClassesServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
/**
* Service provider for the non-static utils classes in the Automattic\WooCommerce\src namespace.
*/
class UtilsClassesServiceProvider extends AbstractServiceProvider {
/**
* The classes/interfaces that are serviced by this service provider.
*
* @var array
*/
protected $provides = array(
DatabaseUtil::class,
);
/**
* Register the classes.
*/
public function register() {
$this->share( DatabaseUtil::class );
}
}

View File

@ -1,46 +0,0 @@
<?php
/**
* A class of utilities for dealing with Database management.
*/
namespace Automattic\WooCommerce\Utilities;
use Automattic\Jetpack\Constants;
class DBUtil {
/**
* Verify if the table(s) already exists.
*
* @param string $table_creation_sql Schema definition to check against.
*
* return array List of missing tables.
*/
public static function verify_database_tables_exist( $table_creation_sql ) {
require_once( Constants::get_constant( 'ABSPATH' ) . 'wp-admin/includes/upgrade.php' );
$missing_tables = array();
$queries = dbDelta( $table_creation_sql, false );
foreach ( $queries as $table_name => $result ) {
if ( "Created table $table_name" === $result ) {
$missing_tables[] = $table_name;
}
}
return $missing_tables;
}
/**
* Create DB tables for passed schema. Currently a wrapper for dbDelta.
*
* @param string $table_creation_sql Table SQL
*
* @return array List of tables that we were not able to create.
*/
public static function create_database_tables( $table_creation_sql ) {
require_once( Constants::get_constant( 'ABSPATH' ) . 'wp-admin/includes/upgrade.php' );
dbDelta( $table_creation_sql );
return self::verify_database_tables_exist( $table_creation_sql );
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* DatabaseUtil class file.
*/
namespace Automattic\WooCommerce\Internal\Utilities;
/**
* A class of utilities for dealing with the database.
*/
class DatabaseUtil {
/**
* Wrapper for the WordPress dbDelta function, allows to execute a series of SQL queries.
*
* @param string $queries The SQL queries to execute.
* @param bool $execute Ture to actually execute the queries, false to only simulate the execution.
* @return array The result of the execution (or simulation) from dbDelta.
*/
public function dbdelta( string $queries = '', bool $execute = true ): array {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
return dbDelta( $queries, $execute );
}
/**
* Given a set of table creation SQL statements, check which of the tables are currently missing in the database.
*
* @param string $creation_queries The SQL queries to execute ("CREATE TABLE" statements, same format as for dbDelta).
* @return array An array containing the names of the tables that currently don't exist in the database.
*/
public function get_missing_tables( string $creation_queries ): array {
$dbdelta_output = $this->dbdelta( $creation_queries, false );
$parsed_output = $this->parse_dbdelta_output( $dbdelta_output );
return $parsed_output['created_tables'];
}
/**
* Parses the output given by dbdelta and returns information about it.
*
* @param array $dbdelta_output The output from the execution of dbdelta.
* @return array[] An array containing a 'created_tables' key whose value is an array with the names of the tables that have been (or would have been) created.
*/
public function parse_dbdelta_output( array $dbdelta_output ): array {
$created_tables = array();
foreach ( $dbdelta_output as $table_name => $result ) {
if ( "Created table $table_name" === $result ) {
$created_tables[] = $table_name;
}
}
return array( 'created_tables' => $created_tables );
}
/**
* Drops a database table.
*
* @param string $table_name The name of the table to drop.
* @param bool $add_prefix True if the table name passed needs to be prefixed with $wpdb->prefix before processing.
*/
public function drop_database_table( string $table_name, bool $add_prefix = false ) {
global $wpdb;
if ( $add_prefix ) {
$table_name = $wpdb->prefix . $table_name;
}
//phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "DROP TABLE IF EXISTS {$table_name}" );
}
}