Skip to content

Instantly share code, notes, and snippets.

@Ellrion
Created September 14, 2016 12:36

Revisions

  1. Ellrion created this gist Sep 14, 2016.
    165 changes: 165 additions & 0 deletions BaseCommand.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,165 @@
    <?php

    namespace App\Console;

    use Illuminate\Console\Command;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;

    class BaseCommand extends Command
    {
    /**
    * Обычный запуск команды.
    * Возможно конкурентное выполнение команды.
    */
    const RUN_SIMPLE = 0;

    /**
    * Эксклюзивный запуск команды.
    * Выполняется только один экземпляр команды.
    */
    const RUN_BLOCKING = 1;

    /**
    * Организует очередь экземпляров команд.
    * Выполняется только один экземпляр команды,
    * каждый последующий запуск команды помещает ее в очередь на выполнение.
    */
    const RUN_QUEUE = 2;

    /**
    * Управление запуском команды. Задается константой с префиксом "RUN_".
    *
    * @var int
    */
    protected $behavior = self::RUN_SIMPLE;

    /**
    * Дескриптор файла блокировки.
    *
    * @var resource
    */
    protected $lockHandle;

    /**
    * Возвращает путь к файлу блокировки.
    * По-умолчанию, это "storage/lock/(имя команды).lock".
    *
    * @return string
    */
    protected function getLockPath()
    {
    return storage_path() . '/lock/' . strtolower(str_replace(':', '_', $this->name)) . '.lock';
    }

    /**
    * Возвращает флаги для захвата блокировки.
    *
    * @return int
    */
    protected function getLockFlags()
    {
    switch ($this->behavior) {
    case self::RUN_BLOCKING:
    return LOCK_EX | LOCK_NB;

    case self::RUN_QUEUE:
    return LOCK_EX;

    default:
    return LOCK_UN;
    }
    }

    /**
    * Захват блокировки.
    *
    * @return bool
    */
    protected function lock()
    {
    if (is_resource($this->lockHandle)) {
    $this->free();
    }

    $this->lockHandle = fopen($this->getLockPath(), 'c');

    $lock = false !== $this->lockHandle && flock($this->lockHandle, $this->getLockFlags());

    if (false === $lock) {
    $this->lockHandle = null;
    }

    return $lock;
    }

    /**
    * Освобождение блокировки.
    *
    * @return bool
    */
    protected function free()
    {
    if (!is_resource($this->lockHandle)) {
    return false;
    }

    fflush($this->lockHandle);

    flock($this->lockHandle, LOCK_UN);

    fclose($this->lockHandle);

    return $this->removeLockFile();
    }

    /**
    * Удаление файла блокировки
    *
    * @return bool
    */
    protected function removeLockFile()
    {
    try {
    $file = $this->getLockPath();
    if (is_file($file) && !is_link($file)) {
    if (@unlink($file) === false) {
    throw new \Exception(sprintf('Failed to remove lock file "%s".', $file));
    }
    } else {
    return false;
    }

    return true;
    } catch (\Exception $e) {
    return false;
    }
    }

    /**
    * Проверяет, используется ли блокировка в управлении запуском.
    *
    * @return bool
    */
    protected function isUsedLock()
    {
    return $this->behavior !== self::RUN_SIMPLE;
    }

    /**
    * Execute the console command.
    *
    * @param \Symfony\Component\Console\Input\InputInterface $input
    * @param \Symfony\Component\Console\Output\OutputInterface $output
    * @return mixed
    */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
    if (!$this->isUsedLock() || $this->lock()) {
    $method = method_exists($this, 'handle') ? 'handle' : 'fire';
    call_user_func([$this, $method]);
    }

    $this->free();
    }
    }