1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Alpixel\Bundle\CronBundle\Command; |
4
|
|
|
|
5
|
|
|
use Alpixel\Bundle\CronBundle\Entity\CronJob; |
6
|
|
|
use Alpixel\Bundle\CronBundle\Entity\CronJobResult; |
7
|
|
|
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; |
8
|
|
|
use Symfony\Component\Console\Input\ArgvInput; |
9
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
10
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
11
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
12
|
|
|
|
13
|
|
|
class CronRunCommand extends ContainerAwareCommand |
14
|
|
|
{ |
15
|
|
|
protected function configure() |
16
|
|
|
{ |
17
|
|
|
$this->setName('cron:run') |
18
|
|
|
->setDescription('Runs any currently schedule cron jobs') |
19
|
|
|
->addArgument('job', InputArgument::OPTIONAL, 'Run only this job (if enabled)'); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
23
|
|
|
{ |
24
|
|
|
$start = microtime(true); |
25
|
|
|
$entityManager = $this->getContainer()->get('doctrine.orm.entity_manager'); |
26
|
|
|
$jobRepo = $entityManager->getRepository('CronBundle:CronJob'); |
27
|
|
|
|
28
|
|
|
$jobsToRun = []; |
29
|
|
|
if ($jobName = $input->getArgument('job')) { |
30
|
|
|
try { |
31
|
|
|
$jobObj = $jobRepo->findOneByCommand($jobName); |
32
|
|
|
if ($jobObj->getEnabled()) { |
33
|
|
|
$jobsToRun = [$jobObj]; |
34
|
|
|
} |
35
|
|
|
} catch (\Exception $e) { |
36
|
|
|
$output->writeln("Couldn't find a job by the name of $jobName"); |
37
|
|
|
|
38
|
|
|
return CronJobResult::FAILED; |
39
|
|
|
} |
40
|
|
|
} else { |
41
|
|
|
$jobsToRun = $jobRepo->findDueTasks(); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
$jobCount = count($jobsToRun); |
45
|
|
|
$output->writeln("Running $jobCount jobs:"); |
46
|
|
|
|
47
|
|
|
foreach ($jobsToRun as $job) { |
48
|
|
|
$this->runJob($job, $output); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
$entityManager->flush(); |
52
|
|
|
|
53
|
|
|
$end = microtime(true); |
54
|
|
|
$duration = sprintf('%0.2f', $end - $start); |
55
|
|
|
$output->writeln("Cron run completed in $duration seconds"); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
protected function runJob(CronJob $job, OutputInterface $output) |
59
|
|
|
{ |
60
|
|
|
$entityManager = $this->getContainer()->get('doctrine.orm.entity_manager'); |
61
|
|
|
$output->write('Running '.$job->getCommand().': '); |
62
|
|
|
|
63
|
|
|
try { |
64
|
|
|
$commandToRun = $this->getApplication()->get($job->getCommand()); |
65
|
|
|
} catch (\Symfony\Component\Console\Exception\InvalidArgumentException $ex) { |
66
|
|
|
$output->writeln(' skipped (command no longer exists)'); |
67
|
|
|
$this->recordJobResult($entityManager, $job, 0, 'Command no longer exists', CronJobResult::SKIPPED); |
|
|
|
|
68
|
|
|
|
69
|
|
|
// No need to reschedule non-existant commands |
70
|
|
|
return; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
$emptyInput = new ArgvInput(); |
74
|
|
|
$jobOutput = new MemoryWriter(); |
75
|
|
|
|
76
|
|
|
$jobStart = microtime(true); |
77
|
|
|
try { |
78
|
|
|
$returnCode = $commandToRun->execute($emptyInput, $jobOutput); |
79
|
|
|
} catch (\Exception $ex) { |
80
|
|
|
$returnCode = CronJobResult::FAILED; |
81
|
|
|
$jobOutput->writeln(''); |
82
|
|
|
$jobOutput->writeln('Job execution failed with exception '.get_class($ex).':'); |
83
|
|
|
$jobOutput->writeln($ex->__toString()); |
84
|
|
|
} |
85
|
|
|
$jobEnd = microtime(true); |
86
|
|
|
|
87
|
|
|
// Clamp the result to accepted values |
88
|
|
|
if ($returnCode < CronJobResult::RESULT_MIN || $returnCode > CronJobResult::RESULT_MAX) { |
89
|
|
|
$returnCode = CronJobResult::FAILED; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
// Output the result |
93
|
|
|
$statusStr = 'unknown'; |
94
|
|
|
if ($returnCode == CronJobResult::SKIPPED) { |
95
|
|
|
$statusStr = 'skipped'; |
96
|
|
|
} elseif ($returnCode == CronJobResult::SUCCEEDED) { |
97
|
|
|
$statusStr = 'succeeded'; |
98
|
|
|
} elseif ($returnCode == CronJobResult::FAILED) { |
99
|
|
|
$statusStr = 'failed'; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$durationStr = sprintf('%0.2f', $jobEnd - $jobStart); |
103
|
|
|
$output->writeln("$statusStr in $durationStr seconds"); |
104
|
|
|
|
105
|
|
|
// Record the result |
106
|
|
|
$this->recordJobResult($job, $jobEnd - $jobStart, $jobOutput->getOutput(), $returnCode); |
107
|
|
|
|
108
|
|
|
// And update the job with it's next scheduled time |
109
|
|
|
$interval = new \DateInterval($job->getInterval()); |
110
|
|
|
$newTime = clone $job->getNextRun(); |
111
|
|
|
$newTime->add($interval); |
112
|
|
|
$job->setNextRun($newTime); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
protected function recordJobResult(CronJob $job, $timeTaken, $output, $resultCode) |
116
|
|
|
{ |
117
|
|
|
$entityManager = $this->getContainer()->get('doctrine.orm.entity_manager'); |
118
|
|
|
|
119
|
|
|
// Create a new CronJobResult |
120
|
|
|
$result = new CronJobResult(); |
121
|
|
|
$result->setJob($job); |
122
|
|
|
$result->setRunTime($timeTaken); |
123
|
|
|
$result->setOutput($output); |
124
|
|
|
$result->setResult($resultCode); |
125
|
|
|
|
126
|
|
|
// Then update associations and persist it |
127
|
|
|
$job->setMostRecentRun($result); |
128
|
|
|
$entityManager->persist($result); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.