Files
woocommerce-subscriptions/includes/abstracts/abstract-wcs-background-updater.php
Prospress Inc 69c6993fcf 2.3.4
2018-08-14 10:18:40 +02:00

149 lines
4.6 KiB
PHP

<?php
/**
* Debug Tool with methods to update data in the background
*
* Add tools for debugging and managing Subscriptions to the
* WooCommerce > System Status > Tools administration screen.
*
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin
* @version 2.3
* @since 2.3
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
} // Exit if accessed directly
/**
* WCS_Background_Updater Class
*
* Provide APIs for a debug tool to update data in the background using Action Scheduler.
*/
abstract class WCS_Background_Updater {
/**
* @var int The amount of time, in seconds, to give the background process to run the update.
*/
protected $time_limit;
/**
* @var string The hook used to schedule background updates.
*/
protected $scheduled_hook;
/**
* Attach callbacks to hooks
*/
public function init() {
// Make sure child classes have defined a scheduled hook, otherwise we can't do background updates.
if ( is_null( $this->scheduled_hook ) ) {
throw new RuntimeException( __CLASS__ . ' must assign a hook to $this->scheduled_hook' );
}
if ( is_null( $this->time_limit ) ) {
$this->time_limit = 60;
// Allow more time for CLI requests, as they're not beholden to script timeouts
if ( $this->is_wp_cli_request() ) {
$this->time_limit *= 3;
}
}
// Allow for each class's time limit to be customised by 3rd party code, as well as all tools' time limits
$this->time_limit = apply_filters( 'wcs_debug_tools_time_limit', $this->time_limit, $this );
// Action scheduled in Action Scheduler for updating data in the background
add_action( $this->scheduled_hook, array( $this, 'run_update' ) );
}
/**
* Get the items to be updated, if any.
*
* @return array An array of items to update, or empty array if there are no items to update.
*/
abstract protected function get_items_to_update();
/**
* Run the update for a single item.
*
* @param mixed $item The item to update.
*/
abstract protected function update_item( $item );
/**
* Update a set of items in the background.
*
* This method will loop over until there are no more items to update, or the process has been running for the
* time limit set on the class @see $this->time_limit, which is 60 seconds by default (wall clock time, not
* execution time).
*
* The $scheduler_hook is rescheduled before updating any items something goes wrong when processing a batch - it's
* scheduled for $this->time_limit in future, so there's little chance of duplicate processes running at the same
* time with WP Cron, but importantly, there is some chance so it should not be used for critical data, like
* payments. Instead, it is intended for use for things like cache updates. It's also a good idea to use an atomic
* update methods to avoid updating something that has already been updated in a separate request.
*
* Importantly, the overlap between the next scheduled update and the current batch is also useful for running
* Action Scheduler via WP CLI, because it will allow for continuous execution of updates (i.e. updating a new
* batch as soon as one batch has execeeded the time limit rather than having to run Action Scheduler via WP CLI
* again later).
*/
public function run_update() {
$this->schedule_background_update();
// If the update is being run via WP CLI, we don't need to worry about the request time, just the processing time for this method
$start_time = $this->is_wp_cli_request() ? gmdate( 'U' ) : WCS_INIT_TIMESTAMP;
do {
$items = $this->get_items_to_update();
foreach ( $items as $item ) {
$this->update_item( $item );
$time_elapsed = ( gmdate( 'U' ) - $start_time );
if ( $time_elapsed >= $this->time_limit ) {
break 2;
}
}
} while ( ! empty( $items ) );
// If we stopped processing the batch because we ran out of items to process, not because we ran out of time, we don't need to run any other batches
if ( empty( $items ) ) {
$this->unschedule_background_updates();
}
}
/**
* Schedule the instance's hook to run in $this->time_limit seconds, if it's not already scheduled.
*/
protected function schedule_background_update() {
if ( false === wc_next_scheduled_action( $this->scheduled_hook ) ) {
wc_schedule_single_action( gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook );
}
}
/**
* Unschedule the instance's hook in Action Scheduler
*/
protected function unschedule_background_updates() {
wc_unschedule_action( $this->scheduled_hook );
}
/**
* Check whether the current request is via WP CLI
*
* @return bool
*/
protected function is_wp_cli_request() {
return ( defined( 'WP_CLI' ) && WP_CLI );
}
}