Last active
July 8, 2020 09:03
-
-
Save FabianSchmick/c32965ffc3a2d84b148c21332f1d96e3 to your computer and use it in GitHub Desktop.
Cron schedule for Symfony commands
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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