Skip to content

Instantly share code, notes, and snippets.

@grachevko
Last active August 8, 2019 06:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grachevko/80ad1f51ebd4e4dce6b7c76ca163dc2c to your computer and use it in GitHub Desktop.
Save grachevko/80ad1f51ebd4e4dce6b7c76ca163dc2c to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
namespace App\Command;
use App\Symfony\EventDispatcher;
use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Konstantin Grachev <me@grachevko.ru>
*/
abstract class LoopedCommand extends Command
{
use EventDispatcher;
/**
* @var \DateInterval
*/
private $interval;
/**
* @var bool
*/
private $terminate = false;
/**
* @var SymfonyStyle
*/
private $io;
/**
* @var Connection|\Doctrine\DBAL\Connection
*/
private $conn;
/**
* @required
*/
public function setConnection(Connection $conn): void
{
$this->conn = $conn;
}
abstract protected function loop(InputInterface $input, OutputInterface $output);
protected function configure(): void
{
$this->addOption('loop-interval', null, InputOption::VALUE_OPTIONAL, 'Interval between run, null to disable');
}
protected function initialize(InputInterface $input, OutputInterface $output): void
{
parent::initialize($input, $output);
if ($interval = $input->getOption('loop-interval')) {
$this->interval = \DateInterval::createFromDateString($interval);
}
$this->io = new class(new SymfonyStyle($input, $output), (bool) $interval) {
/** @var SymfonyStyle */
private $io;
/** @var bool */
private $isLoop;
public function __construct(SymfonyStyle $io, bool $isLoop)
{
$this->io = $io;
$this->isLoop = $isLoop;
}
public function __call($name, $arguments)
{
if (!$this->isLoop) {
return;
}
return call_user_func_array([$this->io, $name], $arguments);
}
};
pcntl_signal(SIGHUP, function (): void {
$this->io->warning('SIGHUP signal was received');
$this->terminate = true;
});
pcntl_signal(SIGINT, function (): void {
exit;
});
}
protected function execute(InputInterface $input, OutputInterface $output): void
{
if ($this->interval) {
$this->io->success(sprintf(
'Loop with "%s" interval has begun',
$this->interval->format('%d days %h hours %i minutes and %s seconds')
));
}
$databaseWaitTimeout = (int) $this->conn->executeQuery('SELECT @@wait_timeout')->fetchColumn();
$index = 0;
while (true) {
pcntl_signal_dispatch();
if ($this->terminate) {
break;
}
if (false === $this->conn->ping()) {
$this->conn->close();
$this->conn->connect();
}
$this->io->write(sprintf('[ %s ] Loop index "%s"', (new \DateTime())->format('Y-m-d H:i:s'), ++$index));
try {
$this->loop($input, $output);
$this->io->writeln(sprintf(' [ %s ]', (new \DateTime())->format('Y-m-d H:i:s')));
} catch (\Exception $e) {
$this->getApplication()->renderException($e, $output);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, new ConsoleErrorEvent($input, $output, $e, $this));
}
if (!$this->interval) {
break;
}
$now = new \DateTimeImmutable();
$seconds = $now->add($this->interval)->getTimestamp() - $now->getTimestamp();
if ($seconds > $databaseWaitTimeout) {
$this->conn->close();
}
sleep($seconds);
}
$this->io->success('Loop successful finished');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment