Базовый класс — waController.
Этот контроллер позволяет реализовать потенциально длительные операции, когда нет возможности избежать срабатывания серверного ограничения на длительность исполнения PHP-скриптов (max_execution_time). Контроллер работает в связке с браузерным скриптом, который должен время от времени отправлять запросы к серверу для получения информации о статусе работы контроллера.
Схематический пример JavaScript-скрипта, отправляющего запросы к контроллеру:
var url = '...'; //URL, обрабатываемый контроллером waLongActionController
var processId = undefined;
var step = function (delay) {
//интервал запросов к серверному контроллеру
delay = delay || 2000;
var timer_id = setTimeout(function () {
$.post(
url,
{processId: processId},
function (r) {
if (!r) {
step(3000);
} else if (r && r.ready) {
//работа контроллера завершена
//устанавливаем индикатор выполнения процесса на значение 100%
$('.progressbar .progressbar-inner').css({
width: '100%'
});
$('.progressbar-description').text('100%');
//получаем отчет о выполненной работе
$.post(url, {processId: processId, cleanup: 1}, function (r) {
if (r.report) {
setTimeout(function () {
//показываем отчет пользователю
$('.progressbar').hide();
$('.report').show();
$('.report').html(r.report);
}, 1000);
}
}, 'json');
} else if (r && r.error) {
//если произошла ошибка, показываем ее текст
//и прекращаем работу
$('.errormsg').text(r.error);
} else {
//если все нормально, обновляем значение индикатора
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 (r && r.warning) {
$('.progressbar-description').append('<i class="icon16 exclamation"></i><p>' + r.warning + '</p>');
}
//переходим к следующему запросу к серверу
step();
}
},
'json'
).error(function () {
//если при выполнении POST-запроса возникла ошибка
//повторим попытку через несколько секунд
step(3000);
});
}, delay);
};
//первый запуск скрипта
$.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');
});
Каждый запрос браузерного скрипта приводит к запуску отдельного процесса на сервере. Из всех процессов, порожденных контроллером, один (self::TYPE_RUNNER) выполняет полезную работу — либо до ее полного завершения, либо до его преждевременного прекращения из-за срабатывания ограничения max_execution_time, а остальные (self::TYPE_MESSENGER) отправляют в браузер информацию о текущем статусе выполнения операции.
Основная логика контроллера должна быть разбита на отдельные фрагменты, каждый из которых гарантированно может быть выполнен до достижения ограничения на время исполнения PHP-скриптов. Логика каждого такого фрагмента должна быть описана в методе step.
Ниже перечислены методы, которые должны быть реализованы разработчиком в собственном контроллере.
Методы
-
finish
Определяет, можно ли очистить связанные с выполнением операции временные данные (файлы) после ее завершения.
-
info
Возвращает в браузер информацию о статусе выполнения операции.
-
init
Инициализация значений, которые могут использоваться в работе контроллера.
-
isDone
Определяет факт завершения операции.
-
step
Логика выполнения отдельного фрагмента операции.
protected function finish ($filename)
Определяет, можно ли очистить связанные с выполнением операции временные данные (файлы) после ее завершения.
Параметры
-
$filename
Имя файла, гарантированно содержащего корректную информацию о последнем успешно выполненном фрагменте операции.
Пример
protected function finish($filename)
{
//Для надежности в некоторых ситуациях вы можете здесь проанализировать
//содержимое файла $filename
$this->info();
if ($this->getRequest()::post('cleanup')) {
return true;
}
return false;
}
protected function info ()
Возвращает в браузер информацию о статусе выполнения операции.
Пример
protected function info()
{
$interval = 0;
if (!empty($this->data['timestamp'])) {
$interval = time() - $this->data['timestamp'];
}
$response = [
'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 ()
Инициализация значений, которые могут использоваться в работе контроллера.
Пример
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 ()
Определяет факт завершения операции. Как только метод вернет true, прекращается выполнение основной логики контроллера в методе step.
Пример
protected function isDone()
{
return $this->data['offset'] >= $this->data['products_total_count'];
}
protected function step ()
Логика выполнения отдельного фрагмента операции.
Пример
protected function step()
{
static $products;
if (empty($products)) {
$products = ...; // получаем новые данные для обновления записей в БД
$this->data['hint'] = _wp('Applying new values...');
}
static $product_model;
if (empty($product_model)) {
$product_model = new shopProductModel();
}
// ограничиваем количество обновляемых записей база данных небольшим
// числом, чтобы при их обновлении гарантированно не выйти за размер
// ограничения max_execution_time
$chunk_size = 10;
$chunk = array_slice($products, $this->data['offset'], $chunk_size);
foreach ($chunk as $product) {
$product_model->updateById($product['id'], [
'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;
}









