Skip to content

Instantly share code, notes, and snippets.

@FabianSchmick
Last active July 8, 2020 09:03
Show Gist options
  • Save FabianSchmick/c32965ffc3a2d84b148c21332f1d96e3 to your computer and use it in GitHub Desktop.
Save FabianSchmick/c32965ffc3a2d84b148c21332f1d96e3 to your computer and use it in GitHub Desktop.
Cron schedule for Symfony commands
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Cron
*
* @ORM\Table(name="cron")
* @ORM\Entity(repositoryClass="AppBundle\Repository\CronRepository")
*/
class Cron
{
/**
* @var int
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @Assert\NotBlank()
* @ORM\Column(type="string", length=255)
*/
private $command;
/**
* @var string|null
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $args;
/**
* @var string|null
*
* @ORM\Column(type="text", length=16777215, nullable=true)
*/
private $lastResult;
/**
* @var int
*
* @ORM\Column(type="integer", nullable=true)
*/
private $processId;
/**
* @var string
*
* @Assert\NotBlank()
* @ORM\Column(name="`interval`", type="string", length=31)
*/
private $interval;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime", nullable=true)
*/
private $started;
/**
* @var \DateTime
*
* @ORM\Column(type="datetime", nullable=true)
*/
private $ended;
public function __toString()
{
return $this->getCommand() . ' ' . $this->getArgs();
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set command.
*
* @param string $command
*
* @return Cron
*/
public function setCommand($command)
{
$this->command = $command;
return $this;
}
/**
* Get command.
*
* @return string
*/
public function getCommand()
{
return $this->command;
}
/**
* Set args.
*
* @param string|null $args
*
* @return Cron
*/
public function setArgs($args = null)
{
$this->args = $args;
return $this;
}
/**
* Get args.
*
* @return string|null
*/
public function getArgs()
{
return $this->args;
}
/**
* Set lastResult.
*
* @param string|null $lastResult
*
* @return Cron
*/
public function setLastResult($lastResult = null)
{
$this->lastResult = $lastResult;
return $this;
}
/**
* Get lastResult.
*
* @return string|null
*/
public function getLastResult()
{
return $this->lastResult;
}
/**
* Set processId.
*
* @param int $processId
*
* @return Cron
*/
public function setProcessId($processId = null)
{
$this->processId = $processId;
return $this;
}
/**
* Get processId.
*
* @return int
*/
public function getProcessId()
{
return $this->processId;
}
/**
* Set interval.
*
* @param string $interval
*
* @return Cron
*/
public function setInterval($interval)
{
$this->interval = $interval;
return $this;
}
/**
* Get interval.
*
* @return string
*/
public function getInterval()
{
return $this->interval;
}
/**
* @return \DateTime
*/
public function getStarted()
{
return $this->started;
}
/**
* @param \DateTime $started
*/
public function setStarted($started)
{
$this->started = $started;
}
/**
* @return \DateTime
*/
public function getEnded()
{
return $this->ended;
}
/**
* @param \DateTime $ended
*/
public function setEnded($ended)
{
$this->ended = $ended;
}
}
<?php
namespace AppBundle\Command;
use AppBundle\Service\CronService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
class CronCommand extends Command
{
private $entityManager;
private $cronService;
public function __construct(EntityManagerInterface $entityManager, CronService $cronService)
{
parent::__construct();
$this->entityManager = $entityManager;
$this->cronService = $cronService;
}
protected function configure()
{
$this
->setName('bimworx:cron')
->setDescription('Execute cronjobs from admin')
->setHelp('Executes all crons in the correct interval from admin dashboard');
}
/** https://www.tomasvotruba.cz/blog/2018/02/05/how-to-run-symfony-processes-asynchronously/ */
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->text('Executing cronjobs...');
$io->newLine();
$crons = $this->entityManager->getRepository('AppBundle:Cron')->findAll();
$activeProcesses = [];
$countExecuted = 0;
foreach ($crons as $key => $cron) {
if (!$this->cronService->isAllowedToBeExecuted($cron)) {
continue;
}
$process = $this->cronService->execute($cron);
// store process for later, so we evaluate it's finished
$activeProcesses[] = [
'process' => $process,
'key' => $key,
];
$countExecuted++;
}
while (count($activeProcesses)) {
foreach ($activeProcesses as $i => $process) {
if ($process['process']->isRunning()) {
continue;
}
$cron = $this->cronService->finish($crons[$process['key']], $process['process']);
unset($activeProcesses[$i]); // specific process is finished, so we remove it
}
// check every second
sleep(1);
}
// here we know that all are finished
if (!$countExecuted) {
$io->note('No cronjobs were allowed to be executed');
return 0;
}
$io->success($countExecuted.' cronjobs executed successfully!');
return 0;
}
}
<?php
namespace AppBundle\Service;
use AppBundle\Entity\Cron;
use Cron\CronExpression;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Process\Process;
class CronService
{
private $projectDir;
private $entityManager;
public function __construct(EntityManagerInterface $entityManager, $projectDir)
{
$this->projectDir = $projectDir;
$this->entityManager = $entityManager;
}
public function isAllowedToBeExecuted(Cron $cron)
{
if ($cron->getProcessId() && posix_getpgid($cron->getProcessId())) {
return false;
}
$interval = CronExpression::factory($cron->getInterval());
return $interval->getNextRunDate($cron->getEnded() ? $cron->getEnded() : '10 minutes ago') < new \DateTime() ? true : false;
}
/**
* @param Cron $cron
* @return Process
*/
public function execute(Cron $cron)
{
$cmdBase = 'php bin/console ';
$cmd = $cmdBase.$cron->getCommand().' '.$cron->getArgs();
if (php_sapi_name() === 'cli') {
$process = new Process($cmd, $this->projectDir);
} else {
$process = new Process($cmd, $this->projectDir, ['PATH' => "~/.bin:{$_SERVER['PATH']}"]);
}
$process->start();
$cron->setStarted(new \DateTime());
$cron->setEnded(null);
$cron->setProcessId($process->getPid());
$this->entityManager->persist($cron);
$this->entityManager->flush();
return $process;
}
/**
* @param Cron $cron
* @param Process $process
* @return Cron
*/
public function finish(Cron $cron, Process $process)
{
$cron->setProcessId(null);
$cron->setEnded(new \DateTime());
if ($process->isSuccessful()) {
$cron->setLastResult($process->getOutput());
} else {
$cron->setLastResult($process->getErrorOutput());
}
$this->entityManager->persist($cron);
$this->entityManager->flush();
return $cron;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment