HEX
Server: nginx/1.18.0
System: Linux test-ipsremont 5.4.0-214-generic #234-Ubuntu SMP Fri Mar 14 23:50:27 UTC 2025 x86_64
User: ips (1000)
PHP: 8.0.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/heifetz/heifetz-app/crons/AbstractCron.php
<?php

declare(strict_types=1);

namespace Crons;

use App\Exceptions\CrmException;
use Core\DbLib\AbstractDbDriver;
use Core\DbLib\DbDriver;
use Helpers\Converter;
use Helpers\FileHelper;
use Helpers\Output;
use Helpers\StringHelper;
use Models\CronStatistic;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Throwable;

abstract class AbstractCron
{

    const EXIT_CODE_OK = 0;
    const EXIT_CODE_UNSPECIFIED_ERROR = 1;

    const FONT_GREEN = "\033[32m\033[1m";
    const FONT_LIGHT_RED = "\033[91m\033[1m";
    const NORMAL = "\033[0m";
    const CRONS_LOCK_DIR = ROOT . '/tmp/crons';

    static array $help = [];

    /** @var AbstractDbDriver $db */
    protected AbstractDbDriver $db;

    protected string $className;

    private Logger $logger;
    private array $logData = [];

    protected bool $debug = false;

    protected bool $verbose = false;

    protected string $title = '';

    protected bool $showOutput = false;

    protected bool $stopCron = false;

    protected string $cronError = '';

    protected bool $echo = true;

    protected bool $needLock = false;
    protected bool $needEchoLock = true;

    protected bool $onlyForProd = false;
    protected bool $allCompanies = true;

    protected ?string $disableReason = null;

    public function __construct()
    {
        $this->db = DbDriver::getInstance();
        $this->className = StringHelper::getClassName(static::class);

        $this->logger = new Logger('crons');
        $fileName = ROOT . '/logs/' . date('Y-m-d_') . $this->className;
        $this->logger->pushHandler(new StreamHandler($fileName . '.log'));
        $this->logger->pushHandler(new StreamHandler($fileName . '.err.log', Logger::ERROR));
    }

    protected function disableMemoryLimit(): void
    {
        ini_set('memory_limit', '-1');
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function disableOutput(): static
    {
        $this->showEcho(false)->showOutput(false);

        return $this;
    }

    public function showOutput($show = true): static
    {
        $this->showOutput = $show;

        return $this;
    }

    public function showEcho($show = true): static
    {
        $this->echo = $show;

        return $this;
    }

    public function setVerbose(bool $verbose = false): static
    {
        $this->verbose = $verbose;

        return $this;
    }

    public function setDebug(?bool $debug = true): static
    {
        $this->debug = $debug ?? false;

        return $this;
    }

    public function writeLog(string $message): void
    {
        $this->logger->info($message, $this->logData);
    }

    protected function setLogDataFields(string $field, $value): static
    {
        $this->logData[$field] = $value;

        return $this;
    }

    protected function removeLogDataFields(string $field): static
    {
        unset($this->logData[$field]);

        return $this;
    }

    abstract public function execute();

    protected function title(string $message): void
    {
        if ($this->showOutput) {
            Output::title($message);
        }
    }

    protected function log(string $message): void
    {
        if ($this->showOutput) {
            Output::log($message);
        } elseif ($this->echo) {
            echo $message . PHP_EOL;
        }
    }

    protected function greenLn(string $message): void
    {
        if ($this->showOutput) {
            Output::greenLn($message);
        } elseif ($this->echo) {
            echo $message . PHP_EOL;
        }
    }

    protected function write(string $message = ''): void
    {
        if ($this->showOutput) {
            Output::write($message);
        } elseif ($this->echo) {
            echo ' ' . $message . PHP_EOL;
        }
    }

    protected function line(): void
    {
        if ($this->showOutput) {
            Output::line();
        }
    }

    protected function lineLn(): void
    {
        if ($this->showOutput) {
            Output::line();
            Output::write(PHP_EOL);
        }
    }

    protected function header(string $message): void
    {
        if ($this->showOutput) {
            Output::header($message);
        }
    }

    protected function subHeader(string $message): void
    {
        if ($this->showOutput) {
            Output::subHeader($message);
        }
    }

    protected function warning(string $message): void
    {
        if ($this->showOutput) {
            Output::warning($message);
        }
    }

    protected function redLn(string $message): void
    {
        if ($this->showOutput) {
            Output::redLn($message);
        }
    }

    protected function success(string $message): void
    {
        if ($this->showOutput) {
            Output::success($message);
        }
    }

    protected function error(string $message): void
    {
        if ($this->showOutput) {
            Output::error($message);
        }
    }

    protected function percent($percent): void
    {
        if ($this->showOutput) {
            Output::percent($percent);
        }
    }

    protected function progressBar(float $percent): void
    {
        if ($this->showOutput) {
            $sizes = explode(' ', trim(shell_exec('stty size')));
            $length = (int) $sizes[1];
            echo "\r" . str_repeat(' ', $length);
            $percents = round(($length - 10) / 100 * $percent);
            $progressBar = "\r " . self::FONT_GREEN;
            $progressBar .= str_repeat('█', intval($percents) + 1);
            $progressBar .= str_repeat('▒', intval(max($length - 10 - $percents, 0)));
            echo $progressBar . ' ' . $percent . '%' . self::NORMAL;
        }
    }

    /**
     * @param string $name
     * @param bool $echo
     *
     * @return void
     * @deprecated use $this->withLock()
     */
    public static function lock(string $name, bool $echo = true): void
    {
        $running = exec('ps aux|grep ' . $name . "|grep -v grep|grep -v '/bin/sh'|wc -l");
        if ($running > 1) {
            if ($echo) {
                Output::log('Another instance running. Exit!');
            }
            exit;
        }
    }

    public static function help(): void
    {
        if (empty(static::$help)) {
            return;
        }
        $maxKeySize = 0;
        foreach (static::$help as $key => $description) {
            $keys = explode(',', $key);
            $len = strlen($key);
            $len += 1 === count($keys) ? 4 : 1;
            $maxKeySize = max($maxKeySize, $len);
        }
        $maxKeySize += 2;

        Output::subHeader('Доступные опции:');
        foreach (static::$help as $key => $description) {
            $keys = explode(',', $key);
            $key = 1 === count($keys) ? '    ' . $keys[0] : $keys[0] . ', ' . $keys[1];

            Output::green('  ' . $key);
            echo str_pad($description, $maxKeySize - strlen($key) + strlen($description), pad_type: STR_PAD_LEFT) . PHP_EOL;
        }
        Output::write('');
    }

    public function run(): void
    {
        if ($this->needLock) {
            try {
                FileHelper::checkAndCreateDir(self::CRONS_LOCK_DIR);
            } catch (Throwable) {
                $this->logger->error('Невозможно создать каталог для Lock файлов', $this->logData);
                throw new CrmException('Невозможно создать каталог для Lock файлов');
            }

            $cronName = StringHelper::getClassName(static::class);
            $lockFilePath = self::CRONS_LOCK_DIR . '/' . $cronName . '.lock';
            $lockFile = file_exists($lockFilePath);

            if (!empty($lockFile)) {
                if ($this->needEchoLock) {
                    $this->log('Другой аналогичный процесс уже запущен');
                }

                return;
            }

            try {
                file_put_contents($lockFilePath, 1);
            } catch (Throwable) {
                $this->logger->error('Невозможно создать Lock файл', $this->logData);
                throw new CrmException('Невозможно создать Lock файл');
            }
        }

        $this->showOutput = true;

        if (!is_null($this->disableReason)) {
            $this->warning($this->disableReason);

            return;
        }

        $time = microtime(true);

        $this->title(date('d.m.Y H:i:s') . ': ' . $this->getTitle());

        if ($this->onlyForProd && !IS_PROD) {
            $this->warning('Не работает на тесте');
            $this->success('Готово');

            return;
        }

        if (!$this->stopCron) {
            try {
                static::execute();
                $this->write();
            } catch (Throwable $exception) {
                $error = '';
                if (IS_DEV || IS_TEST) {
                    $error = $exception->getFile() . ':' . $exception->getLine() . PHP_EOL;
                    $error .= $exception->getTraceAsString() . PHP_EOL;
                }
                $this->error($error . $exception->getMessage());
                $this->logger->error($error, $this->logData);
            }
        }

        $time = (int) round(microtime(true) - $time);
        $this->write();
        $text = 'Затраченное время: ' . Converter::secondsToHumanReadable($time);
        $this->logData['_time'] = $time;
        $this->log($text);

        $memory = memory_get_peak_usage();
        $this->log('Максимально было затрачено памяти: ' . Converter::getHumanReadableMem($memory));
        $this->logData['_memory'] = $memory;

        CronStatistic::addStatistic($memory, $time, $this->className);

        if ($this->stopCron) {
            $error = empty($this->cronError) ? 'Крон выполнился не до конца' : $this->cronError;
            $this->logger->error($error, $this->logData);
            $this->error($error);
        } else {
            $this->success('Готово');
        }

        if ($this->needLock && !empty($lockFilePath) && file_exists($lockFilePath)) {
            unlink($lockFilePath);
        }

        $this->logger->info('Команда успешно выполнена', $this->logData);
    }

}