Parent class: waController
.
This controller enables you to implement long operations when there is no way to avoid violation of the PHP time limit (max_execution_time
). The controller requires a browser script which must periodically send AJAX requests to the controller to register the operation completion status.
Simple example of a JavaScript snippet used for sending requests to a controller:
var url = '...'; //URL processed by a waLongActionController-based controller var processId = undefined; var step = function (delay) { //interval between requests sent to server controller delay = delay || 2000; var timer_id = setTimeout(function () { $.post( url, {processId: processId}, function (r) { if (!r) { step(3000); } else if (r && r.ready) { //controller operation completed //setting progress indicator value to 100% $('.progressbar .progressbar-inner').css({ width: '100%' }); $('.progressbar-description').text('100%'); //obtaining report on completed operation $.post(url, {processId: processId, cleanup: 1}, function (r) { if (r.report) { setTimeout(function () { //showing report to user $('.progressbar').hide(); $('.report').show(); $('.report').html(r.report); }, 1000); } }, 'json'); } else if (r && r.error) { //in case of an error, show its message to user //and terminate the operation $('.errormsg').text(r.error); } else { //if everything's fine, update progress indicator value if (r && r.progress) { var progress = parseFloat(r.progress.replace(/,/, '.')); $('.progressbar .progressbar-inner').animate({ 'width': progress + '%' }); $('.progressbar-description').text(r.progress); $('.progressbar-hint').text(r.hint); } //if controller returned a warning //show it to user and continue as normal if (r && r.warning) { $('.progressbar-description').append('<i class="icon16 exclamation"></i><p>' + r.warning + '</p>'); } //continue to next request-sending step step(); } }, 'json' ).error(function () { //in case of an error when sending a POST request //repeat in a few seconds step(3000); }); }, delay); }; //initial script start $.post(url, {}, function (r) { if (r && r.processId) { processId = r.processId; step(); } else if (r && r.error) { $('.errormsg').text(r.error); } else { $('.errormsg').text('Server error'); } }, 'json').error(function () { $('.errormsg').text('Server error'); });
Every request from the browser script starts a new process on the server. From all such generated processes, only one (self::TYPE_RUNNER
) executes actual work, either until it's completed or until the process is terminated by the server side configuration set in max_execution_time
parameter. Other processes (self::TYPE_MESSENGER
) only send operation completion information to browser script in return to its repeating AJAX requests.
The main operating logic of the controller must be split into small steps, each being guaranteed unable to violate the max_execution_time
parameter limitation. Each such relatively short iteration of the entire execution cycle must be described in method step
.
Below are described methods which must be implemented by a developer in his own controller.
Methods
-
finish
Determines whether temporary data (files) related to operation execution can be cleared.
-
info
Returns operation completion status to browser script.
-
init
Initialization of values, which can be used for controller operation.
-
isDone
Determines whether operation has been completed.
-
step
Executopn of a single operation step.
protected function finish ($filename)
Determines whether temporary data (files) related to operation execution can be cleared.
Parameters
-
$filename
Name of file which is guaranteed to contain correct information on the last successful operation step.
Example
protected function finish($filename) { //for greater confidence, you may analyze the contents of $filename here $this->info(); if ($this->getRequest()::post('cleanup')) { return true; } return false; }
protected function info ()
Returns operation completion status to browser script.
Example
protected function info() { $interval = 0; if (!empty($this->data['timestamp'])) { $interval = time() - $this->data['timestamp']; } $response = array( 'time' => sprintf('%d:%02d:%02d', floor($interval / 3600), floor($interval / 60) % 60, $interval % 60), 'processId' => $this->processId, 'progress' => 0.0, 'ready' => $this->isDone(), 'offset' => $this->data['offset'], 'hint' => $this->data['hint'], ); $response['progress'] = empty($this->data['products_total_count']) ? 100 : ($this->data['offset'] / $this->data['products_total_count']) * 100; $response['progress'] = sprintf('%0.3f%%', $response['progress']); if ($this->getRequest()->post('cleanup')) { $response['report'] = $this->report(); } echo json_encode($response); }
protected function init ()
Initialization of values, which can be used for controller operation.
Example
protected function init() { $product_model = new shopProductModel(); $this->data['products_total_count'] = $product_model->countAll(); $this->data['offset'] = 0; $this->data['product_id'] = null; $this->data['product_count'] = 0; $this->data['timestamp'] = time(); $this->data['hint'] = _wp('Starting data update...'); }
protected function isDone ()
Determines whether operation has been completed. As soon as this method returns true
, execution of controller's main logic described in step
method is terminated.
Example
protected function isDone() { return $this->data['offset'] >= $this->data['products_total_count']; }
protected function step ()
Executopn of a single operation step.
Example
protected function step() { static $products; if (empty($products)) { $products = ...; //get new data to be applied to existing records $this->data['hint'] = _wp('Applying new values...'); } static $product_model; if (empty($product_model)) { $product_model = new shopProductModel(); } // limiting the number of records to be updated by a relatively small value // so that their update does not take too long and does not violate the // max_execution_time limitation $chunk_size = 10; $chunk = array_slice($products, $this->data['offset'], $chunk_size); foreach ($chunk as $product) { $product_model->updateById($product['id'], array( 'field_name' => $product['field_name'], )); if ($this->data['product_id'] != $product['id']) { sleep(0.2); $this->data['product_id'] = $product['id']; $this->data['product_count'] += 1; } $this->data['offset'] += 1; } return true; }