Skip to content

Commit

Permalink
Add sys:cron:schedule command, closes #257
Browse files Browse the repository at this point in the history
Add a command called `sys:cron:schedule` to schedule cronjobs so they get picked up by the next cron run.

Motivation: Sometimes we want to run a cronjob, but not keep our ssh session or terminal open while running sys:cron:run. 

Was inspired by the excellent Aoe_Scheduler module for M1.

Additional to the new feature some detailed changes regarding code-style and stability / detail fixes that overall improve the command incl. the base class:

* Move the askJobCode method to the cron command base class.

* Format the scheduled at time, without seconds, just like Magento does this.

* Add a basic test for the new sys:cron:schedule command.

* Remove underscores in front of the members in all cron related classes. Also remove the unused 'eventManager' member.

* Remove some more unused members and a const from the cron code (and even more unused members).

* Fix the timezone of the timestamps of the sys:cron:run command to match Magento's behavior.
  • Loading branch information
hostep authored and cmuench committed Oct 11, 2017
1 parent c3be209 commit 261d655
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 82 deletions.
1 change: 1 addition & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ commands:
- N98\Magento\Command\System\Cron\HistoryCommand
- N98\Magento\Command\System\Cron\ListCommand
- N98\Magento\Command\System\Cron\RunCommand
- N98\Magento\Command\System\Cron\ScheduleCommand
- N98\Magento\Command\System\InfoCommand
- N98\Magento\Command\System\MaintenanceCommand
- N98\Magento\Command\System\Setup\ChangeVersionCommand
Expand Down
68 changes: 52 additions & 16 deletions src/N98/Magento/Command/System/Cron/AbstractCronCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,58 @@
namespace N98\Magento\Command\System\Cron;

use N98\Magento\Command\AbstractMagentoCommand;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

abstract class AbstractCronCommand extends AbstractMagentoCommand
{
/**
* @var \Magento\Framework\App\State
*/
protected $_state;

/**
* @var \Magento\Framework\Event\ManagerInterface
*/
protected $_eventManager;
protected $state;

/**
* @var \Magento\Cron\Model\ConfigInterface
*/
protected $_cronConfig;
protected $cronConfig;

/**
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $_scopeConfig;
protected $scopeConfig;

/**
* @var \Magento\Cron\Model\ResourceModel\Schedule\Collection
*/
protected $_cronScheduleCollection;
protected $cronScheduleCollection;

/**
* @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
*/
protected $timezone;

/**
* @param \Magento\Framework\App\State $state
* @param \Magento\Framework\Event\ManagerInterface $eventManager
* @param \Magento\Cron\Model\ConfigInterface $cronConfig
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Cron\Model\ResourceModel\Schedule\Collection $cronScheduleCollection
*/
public function inject(
\Magento\Framework\App\State $state,
\Magento\Framework\Event\ManagerInterface $eventManager,
\Magento\Cron\Model\ConfigInterface $cronConfig,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Cron\Model\ResourceModel\Schedule\Collection $cronScheduleCollection
) {
$this->_state = $state;
$this->_eventManager = $eventManager;
$this->_cronConfig = $cronConfig;
$this->_scopeConfig = $scopeConfig;
$this->_cronScheduleCollection = $cronScheduleCollection;
$this->state = $state;
$this->cronConfig = $cronConfig;
$this->scopeConfig = $scopeConfig;
$this->cronScheduleCollection = $cronScheduleCollection;
$this->timezone = $timezone;
}

/**
Expand All @@ -59,7 +64,7 @@ protected function getJobs()
{
$table = array();

$jobs = $this->_cronConfig->getJobs();
$jobs = $this->cronConfig->getJobs();

foreach ($jobs as $jobGroupCode => $jobGroup) {
foreach ($jobGroup as $job) {
Expand Down Expand Up @@ -87,7 +92,7 @@ protected function getJobs()
*/
protected function getJobConfig($jobCode)
{
foreach ($this->_cronConfig->getJobs() as $jobGroup) {
foreach ($this->cronConfig->getJobs() as $jobGroup) {
foreach ($jobGroup as $job) {
if ($job['name'] == $jobCode) {
return $job;
Expand Down Expand Up @@ -125,4 +130,35 @@ protected function getSchedule(array $job)

return ['m' => '-', 'h' => '-', 'D' => '-', 'M' => '-', 'WD' => '-'];
}

/**
* @param InputInterface $input
* @param OutputInterface $output
* @param array $jobs
* @return string
* @throws \InvalidArgumentException
* @throws \Exception
*/
protected function askJobCode(InputInterface $input, OutputInterface $output, $jobs)
{
foreach ($jobs as $key => $job) {
$question[] = '<comment>[' . ($key + 1) . ']</comment> ' . $job['Job'] . PHP_EOL;
}
$question[] = '<question>Please select job: </question>' . PHP_EOL;

/** @var $dialog DialogHelper */
$dialog = $this->getHelper('dialog');
$jobCode = $dialog->askAndValidate(
$output,
$question,
function ($typeInput) use ($jobs) {
if (!isset($jobs[$typeInput - 1])) {
throw new \InvalidArgumentException('Invalid job');
}
return $jobs[$typeInput - 1]['Job'];
}
);

return $jobCode;
}
}
11 changes: 3 additions & 8 deletions src/N98/Magento/Command/System/Cron/HistoryCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@

class HistoryCommand extends AbstractCronCommand
{
/**
* @var array
*/
protected $infos;

protected function configure()
{
$this
Expand Down Expand Up @@ -47,20 +42,20 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

$timezone = $input->getOption('timezone')
? $input->getOption('timezone') : $this->_scopeConfig->getValue('general/locale/timezone');
? $input->getOption('timezone') : $this->scopeConfig->getValue('general/locale/timezone');

if (!$input->getOption('format')) {
$output->writeln('<info>Times shown in <comment>' . $timezone . '</comment></info>');
}

$date = $this->getObjectManager()->create('Magento\Framework\Stdlib\DateTime\DateTime');
$offset = $date->calculateOffset($timezone);
$this->_cronScheduleCollection
$this->cronScheduleCollection
->addFieldToFilter('status', array('neq' => Schedule::STATUS_PENDING))
->addOrder('finished_at', \Magento\Framework\Data\Collection::SORT_ORDER_DESC);

$table = array();
foreach ($this->_cronScheduleCollection as $job) {
foreach ($this->cronScheduleCollection as $job) {
$table[] = array(
$job->getJobCode(),
$job->getStatus(),
Expand Down
5 changes: 0 additions & 5 deletions src/N98/Magento/Command/System/Cron/ListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@

class ListCommand extends AbstractCronCommand
{
/**
* @var array
*/
protected $infos;

protected function configure()
{
$this
Expand Down
48 changes: 5 additions & 43 deletions src/N98/Magento/Command/System/Cron/RunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,12 @@
namespace N98\Magento\Command\System\Cron;

use Magento\Cron\Model\Schedule;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class RunCommand extends AbstractCronCommand
{
const REGEX_RUN_MODEL = '#^([a-z0-9_]+/[a-z0-9_]+)::([a-z0-9_]+)$#i';
/**
* @var array
*/
protected $infos;

protected function configure()
{
$this
Expand Down Expand Up @@ -70,58 +63,27 @@ protected function execute(InputInterface $input, OutputInterface $output)
);

try {
$schedule = $this->_cronScheduleCollection->getNewEmptyItem();
$schedule = $this->cronScheduleCollection->getNewEmptyItem();
$schedule
->setJobCode($jobCode)
->setStatus(Schedule::STATUS_RUNNING)
->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', $this->timezone->scopeTimeStamp()))
->save();

$this->_state->emulateAreaCode('crontab', $callback, array($schedule));
$this->state->emulateAreaCode('crontab', $callback, array($schedule));

$schedule
->setStatus(Schedule::STATUS_SUCCESS)
->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', $this->timezone->scopeTimeStamp()))
->save();
} catch (Exception $e) {
$schedule
->setStatus(Schedule::STATUS_ERROR)
->setMessages($e->getMessage())
->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', $this->timezone->scopeTimeStamp()))
->save();
}

$output->writeln('<info>done</info>');
}

/**
* @param InputInterface $input
* @param OutputInterface $output
* @param array $jobs
* @return string
* @throws \InvalidArgumentException
* @throws \Exception
*/
protected function askJobCode(InputInterface $input, OutputInterface $output, $jobs)
{
foreach ($jobs as $key => $job) {
$question[] = '<comment>[' . ($key + 1) . ']</comment> ' . $job['Job'] . PHP_EOL;
}
$question[] = '<question>Please select job: </question>' . PHP_EOL;

/** @var $dialog DialogHelper */
$dialog = $this->getHelper('dialog');
$jobCode = $dialog->askAndValidate(
$output,
$question,
function ($typeInput) use ($jobs) {
if (!isset($jobs[$typeInput - 1])) {
throw new \InvalidArgumentException('Invalid job');
}
return $jobs[$typeInput - 1]['Job'];
}
);

return $jobCode;
}
}
75 changes: 75 additions & 0 deletions src/N98/Magento/Command/System/Cron/ScheduleCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace N98\Magento\Command\System\Cron;

use Magento\Cron\Model\Schedule;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleCommand extends AbstractCronCommand
{
protected function configure()
{
$this
->setName('sys:cron:schedule')
->addArgument('job', InputArgument::OPTIONAL, 'Job code')
->setDescription('Schedule a cronjob for execution right now, by job code');
$help = <<<HELP
If no `job` argument is passed you can select a job from a list.
HELP;
$this->setHelp($help);
}

/**
* @param InputInterface $input
* @param OutputInterface $output
* @throws \Exception
* @return int|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$jobCode = $input->getArgument('job');
$jobs = $this->getJobs();

if (!$jobCode) {
$this->writeSection($output, 'Cronjob');
$jobCode = $this->askJobCode($input, $output, $jobs);
}

$jobConfig = $this->getJobConfig($jobCode);

if (empty($jobCode) || !isset($jobConfig['instance'])) {
throw new \InvalidArgumentException('No job config found!');
}

$model = $this->getObjectManager()->get($jobConfig['instance']);

if (!$model || !is_callable(array($model, $jobConfig['method']))) {
throw new \RuntimeException(
sprintf(
'Invalid callback: %s::%s does not exist',
$jobConfig['instance'],
$jobConfig['method']
)
);
}

$output->write(
'<info>Scheduling </info><comment>' . $jobConfig['instance'] . '::' . $jobConfig['method'] . '</comment> '
);

$createdAtTime = $this->timezone->scopeTimeStamp();
$scheduledAtTime = $createdAtTime;

$schedule = $this->cronScheduleCollection->getNewEmptyItem();
$schedule
->setJobCode($jobCode)
->setStatus(Schedule::STATUS_PENDING)
->setCreatedAt(strftime('%Y-%m-%d %H:%M:%S', $createdAtTime))
->setScheduledAt(strftime('%Y-%m-%d %H:%M', $scheduledAtTime))
->save();

$output->writeln('<info>done</info>');
}
}
5 changes: 0 additions & 5 deletions src/N98/Magento/Command/System/Store/ListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@

class ListCommand extends AbstractMagentoCommand
{
/**
* @var array
*/
protected $infos;

/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
Expand Down
5 changes: 0 additions & 5 deletions src/N98/Magento/Command/System/Website/ListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@

class ListCommand extends AbstractMagentoCommand
{
/**
* @var array
*/
protected $infos;

/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
Expand Down
24 changes: 24 additions & 0 deletions tests/N98/Magento/Command/System/Cron/ScheduleCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace N98\Magento\Command\System\Cron;

use N98\Magento\Command\TestCase;
use Symfony\Component\Console\Tester\CommandTester;

class ScheduleCommandTest extends TestCase
{
public function testExecute()
{
$application = $this->getApplication();
$application->add(new ListCommand());
$command = $this->getApplication()->find('sys:cron:schedule');

$commandTester = new CommandTester($command);
$commandTester->execute([
'command' => $command->getName(),
'job' => 'backend_clean_cache',
]);

$this->assertContains('Scheduling Magento\Backend\Cron\CleanCache::execute done', $commandTester->getDisplay());
}
}

0 comments on commit 261d655

Please sign in to comment.