Skip to content

Instantly share code, notes, and snippets.

@Ellrion
Created September 14, 2016 12:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ellrion/f8c288bf2e85fb04c48fc0b33b5379ce to your computer and use it in GitHub Desktop.
Save Ellrion/f8c288bf2e85fb04c48fc0b33b5379ce to your computer and use it in GitHub Desktop.
Консольная команда с блокировкой.
<?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();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment