Automattic\WooCommerce\Database\Migrations\CustomOrderTable
PostsToOrdersMigrationController{} │ WC 1.0
This is the main class used to perform the complete migration of orders from the posts table to the custom orders table.
No Hooks.
Usage
$PostsToOrdersMigrationController = new PostsToOrdersMigrationController(); // use class methods
Methods
- public __construct()
- private commit_transaction()
- private db_query( string $query, bool $supress_errors = false )
- public get_migrated_meta_keys()
- private handle_migration_error( array $order_post_ids, array $errors, ?\Exception $exception, ?bool $using_transactions, string $name )
- private maybe_clear_order_datastore_cache_for_ids( array $order_post_ids )
- private maybe_start_transaction()
- public migrate_order( int $order_post_id )
- public migrate_orders( array $order_post_ids )
- private rollback_transaction()
- public verify_migrated_orders( array $order_post_ids )
Notes
- Package: Automattic\WooCommerce\Database\Migrations\CustomOrderTable
PostsToOrdersMigrationController{} PostsToOrdersMigrationController{} code WC 9.6.1
class PostsToOrdersMigrationController { /** * Error logger for migration errors. * * @var \WC_Logger */ private $error_logger; /** * Array of objects used to perform the migration. * * @var \Automattic\WooCommerce\Database\Migrations\TableMigrator[] */ private $all_migrators; /** * The source name to use for logs. */ public const LOGS_SOURCE_NAME = 'posts-to-orders-migration'; /** * PostsToOrdersMigrationController constructor. */ public function __construct() { $this->all_migrators = array(); $this->all_migrators['order'] = new PostToOrderTableMigrator(); $this->all_migrators['order_address_billing'] = new PostToOrderAddressTableMigrator( 'billing' ); $this->all_migrators['order_address_shipping'] = new PostToOrderAddressTableMigrator( 'shipping' ); $this->all_migrators['order_operational_data'] = new PostToOrderOpTableMigrator(); $this->all_migrators['order_meta'] = new PostMetaToOrderMetaMigrator( $this->get_migrated_meta_keys() ); $this->error_logger = wc_get_logger(); } /** * Helper method to get migrated keys for all the tables in this controller. * * @return string[] Array of meta keys. */ public function get_migrated_meta_keys() { $migrated_meta_keys = array(); foreach ( $this->all_migrators as $name => $migrator ) { if ( method_exists( $migrator, 'get_meta_column_config' ) ) { $migrated_meta_keys = array_merge( $migrated_meta_keys, $migrator->get_meta_column_config() ); } } return array_keys( $migrated_meta_keys ); } /** * Migrates a set of orders from the posts table to the custom orders tables. * * @param array $order_post_ids List of post IDs of the orders to migrate. */ public function migrate_orders( array $order_post_ids ): void { $this->error_logger = WC()->call_function( 'wc_get_logger' ); $data = array(); try { foreach ( $this->all_migrators as $name => $migrator ) { $data[ $name ] = $migrator->fetch_sanitized_migration_data( $order_post_ids ); if ( ! empty( $data[ $name ]['errors'] ) ) { $this->handle_migration_error( $order_post_ids, $data[ $name ]['errors'], null, null, $name ); return; } } } catch ( \Exception $e ) { $this->handle_migration_error( $order_post_ids, $data, $e, null, 'Fetching data' ); return; } $using_transactions = $this->maybe_start_transaction(); foreach ( $this->all_migrators as $name => $migrator ) { $results = $migrator->process_migration_data( $data[ $name ] ); $errors = array_unique( $results['errors'] ); $exception = $results['exception']; if ( null === $exception && empty( $errors ) ) { continue; } $this->handle_migration_error( $order_post_ids, $errors, $exception, $using_transactions, $name ); return; } if ( $using_transactions ) { $this->commit_transaction(); } $this->maybe_clear_order_datastore_cache_for_ids( $order_post_ids ); } /** * Log migration errors if any. * * @param array $order_post_ids List of post IDs of the orders to migrate. * @param array $errors List of errors to log. * @param \Exception|null $exception Exception to log. * @param bool|null $using_transactions Whether transactions were used. * @param string $name Name of the migrator. */ private function handle_migration_error( array $order_post_ids, array $errors, ?\Exception $exception, ?bool $using_transactions, string $name ) { $batch = ArrayUtil::to_ranges_string( $order_post_ids ); if ( null !== $exception ) { $exception_class = get_class( $exception ); $this->error_logger->error( "$name: when processing ids $batch: ($exception_class) {$exception->getMessage()}, {$exception->getTraceAsString()}", array( 'source' => self::LOGS_SOURCE_NAME, 'ids' => $order_post_ids, 'exception' => $exception, ) ); } foreach ( $errors as $error ) { $this->error_logger->error( "$name: when processing ids $batch: $error", array( 'source' => self::LOGS_SOURCE_NAME, 'ids' => $order_post_ids, 'error' => $error, ) ); } if ( $using_transactions ) { $this->rollback_transaction(); } else { $this->maybe_clear_order_datastore_cache_for_ids( $order_post_ids ); } } /** * Clear the cache of order data for modified orders during migration if cache is enabled. * * @param array $order_post_ids List of order IDs of the orders to clear from cache. * * @return void */ private function maybe_clear_order_datastore_cache_for_ids( array $order_post_ids ) { if ( OrderUtil::custom_orders_table_datastore_cache_enabled() ) { $orders_table_datastore = wc_get_container()->get( OrdersTableDataStore::class ); if ( is_callable( array( $orders_table_datastore, 'clear_cached_data' ) ) ) { $orders_table_datastore->clear_cached_data( $order_post_ids ); } } } /** * Start a database transaction if the configuration mandates so. * * @return bool|null True if transaction started, false if transactions won't be used, null if transaction failed to start. * * @throws \Exception If the transaction isolation level is invalid. */ private function maybe_start_transaction(): ?bool { $use_transactions = get_option( CustomOrdersTableController::USE_DB_TRANSACTIONS_OPTION, 'yes' ); if ( 'yes' !== $use_transactions ) { return null; } $transaction_isolation_level = get_option( CustomOrdersTableController::DB_TRANSACTIONS_ISOLATION_LEVEL_OPTION, CustomOrdersTableController::DEFAULT_DB_TRANSACTIONS_ISOLATION_LEVEL ); $valid_transaction_isolation_levels = array( 'READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ', 'SERIALIZABLE' ); if ( ! in_array( $transaction_isolation_level, $valid_transaction_isolation_levels, true ) ) { throw new \Exception( "Invalid database transaction isolation level name $transaction_isolation_level" ); } $set_transaction_isolation_level_command = "SET TRANSACTION ISOLATION LEVEL $transaction_isolation_level"; // We suppress errors in transaction isolation level setting because it's not supported by all DB engines, additionally, this might be executing in context of another transaction with a different isolation level. if ( ! $this->db_query( $set_transaction_isolation_level_command, true ) ) { return null; } return $this->db_query( 'START TRANSACTION' ) ? true : null; } /** * Commit the current database transaction. * * @return bool True on success, false on error. */ private function commit_transaction(): bool { return $this->db_query( 'COMMIT' ); } /** * Rollback the current database transaction. * * @return bool True on success, false on error. */ private function rollback_transaction(): bool { return $this->db_query( 'ROLLBACK' ); } /** * Execute a database query and log any errors. * * @param string $query The SQL query to execute. * @param bool $supress_errors Whether to suppress errors. * * @return bool True if the query succeeded, false if there were errors. */ private function db_query( string $query, bool $supress_errors = false ): bool { $wpdb = WC()->get_global( 'wpdb' ); try { if ( $supress_errors ) { $suppress = $wpdb->suppress_errors( true ); } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $query ); if ( $supress_errors ) { $wpdb->suppress_errors( $suppress ); } } catch ( \Exception $exception ) { $exception_class = get_class( $exception ); $this->error_logger->error( "PostsToOrdersMigrationController: when executing $query: ($exception_class) {$exception->getMessage()}, {$exception->getTraceAsString()}", array( 'source' => self::LOGS_SOURCE_NAME, 'exception' => $exception, ) ); return false; } $error = $wpdb->last_error; if ( '' !== $error ) { $this->error_logger->error( "PostsToOrdersMigrationController: when executing $query: $error", array( 'source' => self::LOGS_SOURCE_NAME, 'error' => $error, ) ); return false; } return true; } /** * Verify whether the given order IDs were migrated properly or not. * * @param array $order_post_ids Order IDs. * * @return array Array of failed IDs along with columns. */ public function verify_migrated_orders( array $order_post_ids ): array { $errors = array(); foreach ( $this->all_migrators as $migrator ) { if ( method_exists( $migrator, 'verify_migrated_data' ) ) { $errors = $errors + $migrator->verify_migrated_data( $order_post_ids ); } } return $errors; } /** * Migrates an order from the posts table to the custom orders tables. * * @param int $order_post_id Post ID of the order to migrate. */ public function migrate_order( int $order_post_id ): void { $this->migrate_orders( array( $order_post_id ) ); } }