Для программ типа массовой загрузки страниц, сканирование сети характерно долгое исполнение поставленной задачи. Во время работы таких программ хочется знать сколько осталось до окончания исполнения, иметь возможность прервать и запустить с прерванного места. Для реализации подобного функционала я смастерил класс Job. Принцип работы класса следующий: во время инициализации, классу Job передаётся указатель на инициализированный класс, который содержит public $job_array и public function jobAction($key, $value). Класс Job загружает состояние исполненния, переходит в массиве на ещё необработанный элемент и вызывает метод из переданного класса с текущим ключом и содержимым ячейки массива. Метод исполняется, возращает результат исполнения, результат помещается в массив состояния. Прерывание исполнения выполняется сигналом SIGINT (Ctrl-C). Job.php Code: <?php class Job { public $verbose = true; private $state = []; private $state_filename = ''; //config for printStatus public $printStatus = [ 'size' => '6', // count down size in sprintf %0{$cdf}d 'filename' => "php://stderr", // status output filename ]; public function __construct(&$logger, &$class, string $state_filename) { $this->logger = &$logger; $this->class = &$class; $this->state_filename = $state_filename; $this->state = $this->getState(); // Below need for normal exit if pressed Ctrl-C declare(ticks = 1); pcntl_signal(SIGINT, function($signo) { exit(); }); } public function __destruct() { if (!empty($this->state)) { $this->putState(); $this->state = []; return true; } else { return false; } } public function do(string $job_description) { $count_down = count($this->class->job_array); foreach ($this->class->job_array as $key => $value) { if (key_exists($key, $this->state)) { $count_down -= 1; continue; } if ($this->verbose) { $this->printStatus($job_description, $count_down, $key); } //$this->state[$key] = null; $this->state[$key] = $this->class->jobAction($key, $value); $count_down -= 1; } return true; } public function getState() { if (!file_exists($this->state_filename)) { return []; } if (filesize($this->state_filename) == 0) { return []; } $serialized = file_get_contents($this->state_filename); if (!is_string($serialized)) { $this->logger->error("can't get serialized state from file", [$this->state_filename]); return []; } $state = unserialize($serialized); if (!is_array($state)) { $this->logger->error("can't unserialize state", [$serialized]); return []; } return $state; } public function putState() { $serialized = serialize($this->state); if (!is_string($serialized)) { $this->logger->error("can't serialize state", [$this->state]); return false; } if (!is_int(file_put_contents($this->state_filename, $serialized))) { $this->logger->error("can't put serialized state to file", [$this->state_filename]); return false; } return true; } public function printStatus($job_description, $count_down, $key) { $string = sprintf( "\r{$job_description}:" . " Left: %0{$this->printStatus['size']}d" . " Key: {$key} ", $count_down ); if (is_int(file_put_contents($this->printStatus['filename'], $string, FILE_APPEND))) { return true; } else { return false; } } } JobTest.php Code: <?php class JobTester { public $job_array = []; public function jobAction($key, $value) { sleep(1); if ($key == 0) { $this->job_array[$key] = 1; } else { $this->job_array[$key] = $key * $this->job_array[$key-1]; if ($this->job_array[$key] === INF) { xepHR(); $this->print_job_array(); die; } } } public $error = false; private $data_filename = ''; public function __construct(&$logger, string $data_filename) { $this->logger = &$logger; $this->data_filename = $data_filename; $this->job_array = import($data_filename); if (!is_array($this->job_array)) { $this->logger->error("can't import data array", [$data_filename]); $this->job_array = []; $this->error = true; return false; } return true; } public function __destruct() { if (empty($this->job_array)) { return false; } if (!export($this->data_filename, $this->job_array)) { $this->logger->error("can't export data to file", [$this->data_filename]); return false; } $this->job_array = []; return true; } public function print_job_array() { foreach ($this->job_array as $key => $value) { echo "{$key}! = {$value}; "; } } } import_export.php Code: <?php /** * load and unserialize variable from file * * @param string $filename file with serialized content * * @return unserialized variable * @access public */ function import(string $filename) { $serialized = file_get_contents($filename); if (!is_string($serialized)) { trigger_error("can't get serialized contents from file", E_USER_WARNING); return false; } $variable = unserialize($serialized); if($variable === false) { trigger_error("can't unserialize variable", E_USER_WARNING); return false; } return $variable; } /** * serialize and save variable to file * * @param string $filename file for serialized content * @param void $variable variable to serialize * * @return true if success false if fail * @access public */ function export(string $filename, $variable) { $serialized = serialize($variable); if (!is_string($serialized)) { trigger_error("can't serialize variable", E_USER_WARNING); return false; } $return = file_put_contents($filename, $serialized); if(!is_int($return)) { trigger_error("can't put serialized contents to file", E_USER_WARNING); return false; } return true; } Logger.php Code: <?php class Logger { public function echo(string $message, array $context = []) { file_put_contents("php://stderr", "{$message}\n", FILE_APPEND); if (!empty($context)) { file_put_contents("php://stderr", var_export($context, true), FILE_APPEND); } return true; } public function error(string $message, array $context = []) { return $this->echo("Error: {$message}", $context); } } start.php Code: set_include_path(__DIR__); require_once('import_export.php'); require_once('Logger.php'); require_once('JobTester.php'); require_once('Job.php'); define('JOBTESTER_FN', './factorial.arr'); define('STATE_FN', './state.arr'); $logger = new Logger; if (!file_exists(JOBTESTER_FN)) { $array = []; for ($index = 0; $index < 0x100; $index++) { $array[$index] = 0; } if (!export(JOBTESTER_FN, $array)) { $logger->error("can't export empty factorial array", [JOBTESTER_FN]); exit(1); } } $job_tester = new JobTester($logger, JOBTESTER_FN); $job = new Job($logger, $job_tester, STATE_FN); if (@$argv[1] == 'print_data') { $job_tester->print_job_array(); exit(0); } $job->do("Calculate factorial"); У меня вылезла опечатка которая вызвала несуществующую функцию и привела к фатальному ерору, прога завершилась, а деструкты не выполнинились, на этот случай можно вставить следующий код: Code: register_shutdown_function(function (){ //EcJLu cJLy4uTcR xepHR global $job_tester, $job; $job_tester->__destruct(); $job->__destruct(); });