danchukas /
DenyMultiplyRun
| 1 | <?php |
||
| 2 | declare(strict_types = 1); |
||
| 3 | |||
| 4 | namespace DanchukAS\DenyMultiplyRun; |
||
| 5 | |||
| 6 | use DanchukAS\DenyMultiplyRun\Exception\CloseFileFail; |
||
| 7 | use DanchukAS\DenyMultiplyRun\Exception\ConvertPidFail; |
||
| 8 | use DanchukAS\DenyMultiplyRun\Exception\DeleteFileFail; |
||
| 9 | use DanchukAS\DenyMultiplyRun\Exception\FileExisted; |
||
| 10 | use DanchukAS\DenyMultiplyRun\Exception\LockFileFail; |
||
| 11 | use DanchukAS\DenyMultiplyRun\Exception\OpenFileFail; |
||
| 12 | use DanchukAS\DenyMultiplyRun\Exception\PidBiggerMax; |
||
| 13 | use DanchukAS\DenyMultiplyRun\Exception\PidFileEmpty; |
||
| 14 | use DanchukAS\DenyMultiplyRun\Exception\PidLessMin; |
||
| 15 | use DanchukAS\DenyMultiplyRun\Exception\ProcessExisted; |
||
| 16 | use DanchukAS\DenyMultiplyRun\Exception\ReadFileFail; |
||
| 17 | |||
| 18 | /** |
||
| 19 | * Class denyMultiplyRun |
||
| 20 | * Забороняє паралельний запуск скрипта |
||
| 21 | * |
||
| 22 | * @todo: extract work with file to another lib. |
||
| 23 | * |
||
| 24 | * @package DanchukAS\DenyMultiplyRun |
||
| 25 | */ |
||
| 26 | class DenyMultiplyRun |
||
| 27 | { |
||
| 28 | /** |
||
| 29 | * Для перехвата помилок що не кидають ексепшини. |
||
| 30 | * |
||
| 31 | * @var \Throwable |
||
| 32 | */ |
||
| 33 | private static $lastError; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * @var int |
||
| 37 | */ |
||
| 38 | private static $prevPid; |
||
| 39 | |||
| 40 | |||
| 41 | /** |
||
| 42 | * DenyMultiplyRun constructor. |
||
| 43 | * Унеможливлює створення обєктів цього класу. |
||
| 44 | * Даний клас лише для статичного визова методів. |
||
| 45 | */ |
||
| 46 | private function __construct() |
||
| 47 | { |
||
| 48 | } |
||
| 49 | |||
| 50 | |||
| 51 | /** |
||
| 52 | * Унеможливлює паралельний запуск ще одного процеса pid-файл якого ідентичний. |
||
| 53 | * |
||
| 54 | * Створює файл в якому число-ідентифікатор процеса ОС під яким працює даний код. |
||
| 55 | * Якщо файл існує і процеса з номером що в файлі записаний не існує - |
||
| 56 | * пробує записати теперішній ідентифікатор процеса ОС під яким працює даний код. |
||
| 57 | 8 | * В усіх інших випадках кидає відповідні виключення. |
|
| 58 | * |
||
| 59 | 8 | * @param string $pidFilePath Шлях до файла. Вимоги: користувач під яким запущений |
|
| 60 | * даний код має мати право на створення, читання і зміну |
||
| 61 | * даного файла. |
||
| 62 | 8 | * @throws \Exception |
|
| 63 | 2 | */ |
|
| 64 | 7 | public static function setPidFile(string $pidFilePath) |
|
| 65 | 7 | { |
|
| 66 | 7 | self::preparePidDir($pidFilePath); |
|
| 67 | |||
| 68 | try { |
||
| 69 | 8 | $file_resource = self::createPidFile($pidFilePath); |
|
| 70 | $pid_file_existed = false; |
||
| 71 | } catch (FileExisted $exception) { |
||
| 72 | $file_resource = self::openPidFile($pidFilePath); |
||
| 73 | 7 | $pid_file_existed = true; |
|
| 74 | 7 | } |
|
| 75 | |||
| 76 | 6 | self::lockPidFile($file_resource); |
|
| 77 | 3 | ||
| 78 | 5 | try { |
|
| 79 | self::safeSetPidIntoFile($pid_file_existed, $file_resource); |
||
| 80 | 2 | } finally { |
|
| 81 | self::safeClosePidFile($file_resource); |
||
| 82 | } |
||
| 83 | 4 | } |
|
| 84 | 4 | ||
| 85 | /** |
||
| 86 | 4 | * @param string $pidFilePath |
|
| 87 | 2 | * |
|
| 88 | 1 | * @throws \Exception |
|
| 89 | 2 | */ |
|
| 90 | 2 | private static function preparePidDir($pidFilePath) |
|
| 91 | 2 | { |
|
| 92 | $pid_dir = \dirname($pidFilePath); |
||
| 93 | 2 | ||
| 94 | /** @noinspection MkdirRaceConditionInspection */ |
||
| 95 | 4 | if ('' !== $pid_dir |
|
| 96 | && !\is_dir($pid_dir) |
||
| 97 | 7 | && !\mkdir($pid_dir, 0777, true) |
|
| 98 | 4 | ) { |
|
| 99 | 7 | throw new \RuntimeException('Директорія відсутня і неможливо створити: ' . $pid_dir); |
|
| 100 | } |
||
| 101 | } |
||
| 102 | |||
| 103 | 4 | /** |
|
| 104 | * @param string $pidFilePath |
||
| 105 | * @return resource |
||
| 106 | * @throws FileExisted |
||
| 107 | * @throws \Exception |
||
| 108 | */ |
||
| 109 | private static function createPidFile($pidFilePath) |
||
| 110 | 8 | { |
|
| 111 | // перехоплювач на 1 команду, щоб в разі потреби потім дізнатись причину несправності. |
||
| 112 | 8 | // помилку в записує в self::$lastError |
|
| 113 | 8 | self::startErrorHandle(); |
|
| 114 | |||
| 115 | // собачка потрібна щоб не засоряти логи. |
||
| 116 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
||
| 117 | $pid_file_handle = @fopen($pidFilePath, 'xb'); |
||
| 118 | |||
| 119 | 8 | // Відновлюєм попередній обробник наче нічого і не робили. |
|
| 120 | restore_error_handler(); |
||
| 121 | |||
| 122 | // файл не створений. сталась помилка |
||
| 123 | if (null !== self::$lastError) { |
||
| 124 | self::createPidFileFailed($pidFilePath); |
||
| 125 | } |
||
| 126 | |||
| 127 | 8 | // файл створений успішно. |
|
| 128 | return $pid_file_handle; |
||
|
0 ignored issues
–
show
|
|||
| 129 | } |
||
| 130 | |||
| 131 | 8 | private static function startErrorHandle() |
|
| 132 | { |
||
| 133 | set_error_handler([__CLASS__, 'errorHandle']); |
||
| 134 | |||
| 135 | 8 | self::$lastError = null; |
|
| 136 | } |
||
| 137 | |||
| 138 | 8 | /** |
|
| 139 | * @param $pidFilePath |
||
| 140 | * @throws FileExisted |
||
| 141 | 8 | */ |
|
| 142 | private static function createPidFileFailed($pidFilePath) |
||
| 143 | { |
||
| 144 | 7 | // Файла і нема і не створився - повідомляєм про несправність проекта. |
|
| 145 | if (!is_file($pidFilePath)) { |
||
| 146 | throw new self::$lastError; |
||
| 147 | } |
||
| 148 | |||
| 149 | 7 | // Файл вже існує, тому не створився. |
|
| 150 | throw new FileExisted($pidFilePath); |
||
| 151 | } |
||
| 152 | |||
| 153 | 2 | /** |
|
| 154 | * @param string $pidFilePath |
||
| 155 | * @return resource |
||
| 156 | * @throws \Exception |
||
| 157 | */ |
||
| 158 | private static function openPidFile($pidFilePath) |
||
| 159 | { |
||
| 160 | self::startErrorHandle(); |
||
| 161 | 7 | try { |
|
| 162 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
||
| 163 | 7 | $pid_file_handle = @fopen($pidFilePath, 'rb+'); |
|
| 164 | 7 | } catch (\Throwable $error) { |
|
| 165 | self::$lastError = $error; |
||
| 166 | } finally { |
||
| 167 | 7 | restore_error_handler(); |
|
| 168 | } |
||
| 169 | |||
| 170 | if (null !== self::$lastError) { |
||
| 171 | throw new OpenFileFail((string) self::$lastError); |
||
| 172 | } |
||
| 173 | |||
| 174 | /** @noinspection PhpUndefinedVariableInspection */ |
||
| 175 | 8 | return $pid_file_handle; |
|
|
0 ignored issues
–
show
The expression
return $pid_file_handle could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled. Loading history...
|
|||
| 176 | } |
||
| 177 | 8 | ||
| 178 | 8 | /** |
|
| 179 | 1 | * @param $pidFileResource |
|
| 180 | * |
||
| 181 | * @throws \Exception |
||
| 182 | */ |
||
| 183 | 1 | private static function lockPidFile($pidFileResource) |
|
| 184 | { |
||
| 185 | $locked = flock($pidFileResource, LOCK_EX | LOCK_NB); |
||
| 186 | if (false === $locked) { |
||
| 187 | 1 | $error = self::$lastError; |
|
| 188 | |||
| 189 | // перехоплювач на 1 команду, щоб в разі потреби потім дізнатись причину несправності. |
||
| 190 | 1 | // помилку в записує в self::$lastError |
|
| 191 | self::startErrorHandle(); |
||
| 192 | 1 | ||
| 193 | // собачка потрібна щоб не засоряти логи. |
||
| 194 | 7 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
|
| 195 | $resource_data = @stream_get_meta_data($pidFileResource); |
||
| 196 | |||
| 197 | // Відновлюєм попередній обробник наче нічого і не робили. |
||
| 198 | restore_error_handler(); |
||
| 199 | |||
| 200 | throw new LockFileFail($resource_data['uri'] . ' - ' . $error); |
||
| 201 | 6 | } |
|
| 202 | } |
||
| 203 | |||
| 204 | /** |
||
| 205 | * @param $pid_file_existed |
||
| 206 | 6 | * @param $file_resource |
|
| 207 | * @throws \Exception |
||
| 208 | */ |
||
| 209 | 6 | private static function safeSetPidIntoFile($pid_file_existed, $file_resource) |
|
| 210 | { |
||
| 211 | if ($pid_file_existed) { |
||
| 212 | self::pidNotActual($file_resource); |
||
| 213 | 6 | } |
|
| 214 | |||
| 215 | 3 | $self_pid = getmypid(); |
|
| 216 | self::setPidIntoFile($self_pid, $file_resource); |
||
| 217 | |||
| 218 | if ($pid_file_existed) { |
||
| 219 | self::pidFileUpdated($self_pid); |
||
| 220 | } |
||
| 221 | } |
||
| 222 | |||
| 223 | /** |
||
| 224 | 6 | * @param $file_resource |
|
| 225 | * @throws \Exception |
||
| 226 | * @throws \DanchukAS\DenyMultiplyRun\Exception\ProcessExisted |
||
| 227 | 6 | */ |
|
| 228 | 1 | private static function pidNotActual($file_resource) |
|
| 229 | { |
||
| 230 | self::$prevPid = null; |
||
| 231 | 5 | ||
| 232 | try { |
||
| 233 | self::$prevPid = self::getPidFromFile($file_resource); |
||
| 234 | self::pidNoExisting(self::$prevPid); |
||
| 235 | } catch (PidFileEmpty $exception) { |
||
| 236 | 5 | // if file was once empty is not critical. |
|
| 237 | 5 | // It was after crash daemon. |
|
| 238 | 5 | // There are signal for admin/developer. |
|
| 239 | 5 | trigger_error((string)$exception); |
|
| 240 | 1 | } |
|
| 241 | 1 | self::truncatePidFile($file_resource); |
|
| 242 | } |
||
| 243 | |||
| 244 | /** |
||
| 245 | * @param resource $pidFileResource Дескриптор файла доступного для читання в якому знаходиться PID. |
||
| 246 | * @return int PID з файла |
||
| 247 | 4 | * @throws \Exception |
|
| 248 | 1 | */ |
|
| 249 | 1 | private static function getPidFromFile($pidFileResource) |
|
| 250 | 1 | { |
|
| 251 | // Розмір PID (int в ОС) навряд буде більший ніж розмір int в PHP. |
||
| 252 | // Зазвичай PID має до 5 цифр. |
||
| 253 | 3 | // @todo: if error - warning, error_handler, ... |
|
| 254 | $pid_from_file = fread($pidFileResource, 64); |
||
| 255 | |||
| 256 | |||
| 257 | if (false === $pid_from_file) { |
||
| 258 | throw new ReadFileFail('pid-файл є, але прочитати що в ньому не вдалось.'); |
||
| 259 | } |
||
| 260 | |||
| 261 | 3 | return self::validatePid($pid_from_file); |
|
| 262 | } |
||
| 263 | |||
| 264 | /** |
||
| 265 | * @param string $pid_from_file |
||
| 266 | * @return int |
||
| 267 | 3 | * @throws \DanchukAS\DenyMultiplyRun\Exception\ConvertPidFail |
|
| 268 | * @throws \DanchukAS\DenyMultiplyRun\Exception\PidLessMin |
||
| 269 | * @throws PidBiggerMax |
||
| 270 | * @throws PidFileEmpty |
||
| 271 | 3 | */ |
|
| 272 | private static function validatePid(string $pid_from_file): int |
||
| 273 | 2 | { |
|
| 274 | // На випадок коли станеться виліт скрипта після створення файла і до запису ІД. |
||
| 275 | 1 | self::pidIsNoEmpty($pid_from_file); |
|
| 276 | |||
| 277 | $pid_int = (int) $pid_from_file; |
||
| 278 | |||
| 279 | // verify converting. (PHP_MAX_INT) |
||
| 280 | // verify PID in file is right (something else instead ciphers). |
||
| 281 | if ("{$pid_int}" !== $pid_from_file) { |
||
| 282 | 2 | $message = "pid_int({$pid_int}) !== pid_string($pid_from_file)" |
|
| 283 | . ", or pid_string($pid_from_file) is not Process ID)"; |
||
| 284 | 2 | throw new ConvertPidFail($message); |
|
| 285 | 2 | } |
|
| 286 | |||
| 287 | self::pidIsPossible($pid_int); |
||
| 288 | |||
| 289 | 2 | return $pid_int; |
|
| 290 | 2 | } |
|
| 291 | |||
| 292 | /** |
||
| 293 | 2 | * @param string $pid |
|
| 294 | * @throws PidFileEmpty |
||
| 295 | */ |
||
| 296 | private static function pidIsNoEmpty(string $pid) |
||
| 297 | { |
||
| 298 | if ('' === $pid) { |
||
| 299 | throw new PidFileEmpty(); |
||
| 300 | } |
||
| 301 | 4 | } |
|
| 302 | |||
| 303 | 4 | /** |
|
| 304 | 4 | * Verify possible value of PID in file: less than max possible on OS. |
|
| 305 | 4 | * @param int $pid_int |
|
| 306 | 4 | * @throws PidBiggerMax |
|
| 307 | * @throws PidLessMin |
||
| 308 | */ |
||
| 309 | 4 | private static function pidIsPossible($pid_int) |
|
| 310 | { |
||
| 311 | if ($pid_int < 0) { |
||
| 312 | $message = "PID in file has unavailable value: $pid_int. PID must be no negative."; |
||
| 313 | throw new PidLessMin($message); |
||
| 314 | 7 | } |
|
| 315 | |||
| 316 | 7 | // if PID not available - why it happens ? |
|
| 317 | 7 | // For *nix system |
|
| 318 | $pid_max_storage = '/proc/sys/kernel/pid_max'; |
||
| 319 | if (file_exists($pid_max_storage)) { |
||
| 320 | 7 | $pid_max = (int)file_get_contents($pid_max_storage); |
|
| 321 | if ($pid_max < $pid_int) { |
||
| 322 | $message = "PID in file has unavailable value: $pid_int. In /proc/sys/kernel/pid_max set $pid_max."; |
||
| 323 | throw new PidBiggerMax($message); |
||
| 324 | } |
||
| 325 | } |
||
| 326 | } |
||
| 327 | 7 | ||
| 328 | /** |
||
| 329 | * @param int $pid |
||
| 330 | * |
||
| 331 | 7 | * @throws ProcessExisted |
|
| 332 | */ |
||
| 333 | private static function pidNoExisting($pid) |
||
| 334 | { |
||
| 335 | 7 | if (// Посилає сигнал процесу щоб дізнатись чи він існує. |
|
| 336 | // Якщо true - точно існує. |
||
| 337 | // якщо false - процес може і бути, але запущений під іншим користувачем або інші ситуації. |
||
| 338 | 7 | true === posix_kill($pid, 0) |
|
| 339 | // 3 = No such process (тестувалось в Ubuntu 14, FreeBSD 9. В інших ОС можуть відрізнятись) |
||
| 340 | 7 | // Якщо процеса точно в даний момент нема такого. |
|
| 341 | // Для визова цієї функції необхідний попередній визов posix_kill. |
||
| 342 | || posix_get_last_error() !== 3 |
||
| 343 | ) { |
||
| 344 | throw new ProcessExisted($pid); |
||
| 345 | } |
||
| 346 | } |
||
| 347 | |||
| 348 | /** |
||
| 349 | * @param $pidFileResource |
||
| 350 | * |
||
| 351 | * @throws \Exception |
||
| 352 | */ |
||
| 353 | private static function truncatePidFile($pidFileResource) |
||
| 354 | { |
||
| 355 | $truncated = ftruncate($pidFileResource, 0); |
||
| 356 | if (false === $truncated) { |
||
| 357 | 7 | throw new \RuntimeException('не вдалось очистити pid-файл.'); |
|
| 358 | } |
||
| 359 | |||
| 360 | $cursor_to_begin = rewind($pidFileResource); |
||
| 361 | if (!$cursor_to_begin) { |
||
| 362 | throw new \RuntimeException('не вдалось перемістити курсор на початок pid-файла.'); |
||
| 363 | } |
||
| 364 | } |
||
| 365 | |||
| 366 | 3 | /** |
|
| 367 | * @param int $self_pid |
||
| 368 | * @param $pidFileResource |
||
| 369 | * |
||
| 370 | 3 | * @throws \Exception |
|
| 371 | */ |
||
| 372 | private static function setPidIntoFile($self_pid, $pidFileResource) |
||
| 373 | { |
||
| 374 | $self_pid_str = (string)$self_pid; |
||
| 375 | 3 | $pid_length = strlen($self_pid_str); |
|
| 376 | $write_length = fwrite($pidFileResource, $self_pid_str, $pid_length); |
||
| 377 | if ($write_length !== $pid_length) { |
||
| 378 | throw new \RuntimeException("не вдось записати pid в pid-файл. Записано $write_length байт замість $pid_length"); |
||
| 379 | } |
||
| 380 | 3 | } |
|
| 381 | |||
| 382 | 3 | /** |
|
| 383 | * @param $self_pid |
||
| 384 | */ |
||
| 385 | 3 | private static function pidFileUpdated($self_pid) |
|
| 386 | 1 | { |
|
| 387 | $message_reason = null === self::$prevPid |
||
|
0 ignored issues
–
show
|
|||
| 388 | ? ', but file empty.' |
||
| 389 | : ', but process with contained ID(' . self::$prevPid . ') in it is not exist.'; |
||
| 390 | 2 | $message = 'pid-file exist' . $message_reason |
|
| 391 | . ' pid-file updated with pid this process: ' . $self_pid; |
||
| 392 | |||
| 393 | trigger_error($message); |
||
| 394 | } |
||
| 395 | |||
| 396 | /** |
||
| 397 | * @param $file_resource |
||
| 398 | * @throws \DanchukAS\DenyMultiplyRun\Exception\CloseFileFail |
||
| 399 | */ |
||
| 400 | 9 | private static function safeClosePidFile($file_resource) |
|
| 401 | { |
||
| 402 | try { |
||
| 403 | self::unlockPidFile($file_resource); |
||
| 404 | 9 | } finally { |
|
| 405 | self::closePidFile($file_resource); |
||
| 406 | 9 | } |
|
| 407 | } |
||
| 408 | |||
| 409 | 9 | /** |
|
| 410 | * @param $pidFileResource |
||
| 411 | */ |
||
| 412 | private static function unlockPidFile($pidFileResource) |
||
| 413 | { |
||
| 414 | $unlocked = flock($pidFileResource, LOCK_UN | LOCK_NB); |
||
| 415 | if (false === $unlocked) { |
||
| 416 | trigger_error('не вдось розблокувати pid-файл.'); |
||
| 417 | } |
||
| 418 | } |
||
| 419 | |||
| 420 | /** |
||
| 421 | * @param $pidFileResource |
||
| 422 | * |
||
| 423 | * @throws CloseFileFail |
||
| 424 | */ |
||
| 425 | private static function closePidFile($pidFileResource) |
||
| 426 | { |
||
| 427 | // перехоплювач на 1 команду, щоб в разі потреби потім дізнатись причину несправності. |
||
| 428 | // помилку в записує в self::$lastError |
||
| 429 | self::startErrorHandle(); |
||
| 430 | |||
| 431 | try { |
||
| 432 | // собачка потрібна щоб не засоряти логи. |
||
| 433 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
||
| 434 | $closed = @fclose($pidFileResource); |
||
| 435 | } catch (\Throwable $error) { |
||
| 436 | $closed = false; |
||
| 437 | self::$lastError = $error; |
||
| 438 | } finally { |
||
| 439 | // Відновлюєм попередній обробник наче нічого і не робили. |
||
| 440 | restore_error_handler(); |
||
| 441 | } |
||
| 442 | |||
| 443 | if (false === $closed) { |
||
| 444 | self::closePidFileFailed($pidFileResource); |
||
| 445 | } |
||
| 446 | } |
||
| 447 | |||
| 448 | /** |
||
| 449 | * @param $pidFileResource |
||
| 450 | * @throws CloseFileFail |
||
| 451 | */ |
||
| 452 | private static function closePidFileFailed($pidFileResource) |
||
| 453 | { |
||
| 454 | $file_close_error = self::$lastError; |
||
| 455 | |||
| 456 | // перехоплювач на 1 команду, щоб в разі потреби потім дізнатись причину несправності. |
||
| 457 | // помилку в записує в self::$lastError |
||
| 458 | self::startErrorHandle(); |
||
| 459 | |||
| 460 | try { |
||
| 461 | // собачка потрібна щоб не засоряти логи. |
||
| 462 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
||
| 463 | $resource_data = @stream_get_meta_data($pidFileResource); |
||
| 464 | } catch (\Throwable $error) { |
||
| 465 | $resource_data = ['uri' => '']; |
||
| 466 | } finally { |
||
| 467 | // Відновлюєм попередній обробник наче нічого і не робили. |
||
| 468 | restore_error_handler(); |
||
| 469 | } |
||
| 470 | |||
| 471 | throw new CloseFileFail($resource_data['uri'], 457575, $file_close_error); |
||
| 472 | } |
||
| 473 | |||
| 474 | /** |
||
| 475 | * Відключає встановлену заборону паралельного запуска у яких спільний $pidFilePath |
||
| 476 | * @todo добавити перевірку що цей файл ще для цього процеса, |
||
| 477 | * може цей файл вже був видалений вручну, і створений іншим процесом. |
||
| 478 | * |
||
| 479 | * @param string $pidFilePath |
||
| 480 | * |
||
| 481 | * @throws DeleteFileFail |
||
| 482 | */ |
||
| 483 | public static function deletePidFile($pidFilePath) |
||
| 484 | { |
||
| 485 | // перехоплювач на 1 команду, щоб в разі потреби потім дізнатись причину несправності. |
||
| 486 | // помилку в записує в self::$lastError |
||
| 487 | self::startErrorHandle(); |
||
| 488 | |||
| 489 | try { |
||
| 490 | // собачка потрібна щоб не засоряти логи. |
||
| 491 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
||
| 492 | @unlink($pidFilePath); |
||
| 493 | } catch (\Throwable $error) { |
||
| 494 | self::$lastError = $error; |
||
| 495 | } finally { |
||
| 496 | // Відновлюєм попередній обробник наче нічого і не робили. |
||
| 497 | restore_error_handler(); |
||
| 498 | } |
||
| 499 | |||
| 500 | if (null !== self::$lastError) { |
||
| 501 | throw new DeleteFileFail(self::$lastError); |
||
| 502 | } |
||
| 503 | } |
||
| 504 | |||
| 505 | /** @noinspection MoreThanThreeArgumentsInspection */ |
||
| 506 | /** |
||
| 507 | * @param int $messageType |
||
| 508 | * @param string $messageText |
||
| 509 | * @param string $messageFile |
||
| 510 | * @param int $messageLine |
||
| 511 | * |
||
| 512 | * @return bool |
||
| 513 | */ |
||
| 514 | public static function errorHandle(int $messageType, string $messageText, string $messageFile, int $messageLine) |
||
| 515 | { |
||
| 516 | // добавляємо лише інформацію яка є. |
||
| 517 | // все інше добавляти має обробник самого проекта. |
||
| 518 | $message = "[$messageType] $messageText in $messageFile on line $messageLine"; |
||
| 519 | |||
| 520 | self::$lastError = new \Exception($message); |
||
| 521 | |||
| 522 | // Перехопити перехопили, кидаєм далі обробляти. |
||
| 523 | return false; |
||
| 524 | } |
||
| 525 | } |
||
| 526 |
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.