Completed
Push — master ( 7133c6...a8b998 )
by Christian
08:08 queued 04:58
created

RunCommand::scheduleConfigModel()   B

Complexity

Conditions 4
Paths 7

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 7
nop 2
dl 0
loc 28
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
namespace N98\Magento\Command\System\Cron;
4
5
use Exception;
6
use Mage;
7
use Mage_Core_Model_Config_Element;
8
use Mage_Cron_Model_Schedule;
9
use RuntimeException;
10
use Symfony\Component\Console\Helper\DialogHelper;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Validator\Exception\InvalidArgumentException;
16
17
class RunCommand extends AbstractCronCommand
18
{
19
    const REGEX_RUN_MODEL = '#^([a-z0-9_]+/[a-z0-9_]+)::([a-z0-9_]+)$#i';
20
    /**
21
     * @var array
22
     */
23
    protected $infos;
24
25
    protected function configure()
26
    {
27
        $this
28
            ->setName('sys:cron:run')
29
            ->addArgument('job', InputArgument::OPTIONAL, 'Job code')
30
            ->addOption('schedule', 's', InputOption::VALUE_NONE, 'Schedule cron instead of run with current user')
31
            ->setDescription('Runs a cronjob by job code');
32
        $help = <<<HELP
33
If no `job` argument is passed you can select a job from a list.
34
See it in action: http://www.youtube.com/watch?v=QkzkLgrfNaM
35
If option schedule is present, cron is not launched, but just scheduled immediately in magento crontab.
36
HELP;
37
        $this->setHelp($help);
38
    }
39
40
    /**
41
     * @param InputInterface $input
42
     * @param OutputInterface $output
43
     *
44
     * @return int|void
45
     */
46
    protected function execute(InputInterface $input, OutputInterface $output)
47
    {
48
        $this->detectMagento($output, true);
49
        if (!$this->initMagento()) {
50
            return;
51
        }
52
53
        $jobCode = $input->getArgument('job');
54
        if (!$jobCode) {
55
            $this->writeSection($output, 'Cronjob');
56
            $jobCode = $this->askJobCode($output, $this->getJobs());
57
        }
58
59
        $runConfigModel = $this->getRunConfigModelByJobCode($jobCode);
60
61
        list($callback, $callableName) = $this->getCallbackFromRunConfigModel($runConfigModel, $jobCode);
62
63
        $output->write('<info>Run </info><comment>' . $callableName . '</comment> ');
64
65
        if (!$input->hasOption('schedule')) {
66
            $this->executeConfigModel($callback, $jobCode);
67
        } else {
68
            $this->scheduleConfigModel($callback, $jobCode);
69
        }
70
71
        $output->writeln('<info>done</info>');
72
    }
73
74
    /**
75
     * @param OutputInterface $output
76
     * @param array $jobs array of array containing "job" keyed string entries of job-codes
77
     *
78
     * @return string         job-code
79
     * @throws InvalidArgumentException when user selects invalid job interactively
80
     */
81
    protected function askJobCode(OutputInterface $output, array $jobs)
82
    {
83
        $index = 0;
84
        $keyMap = array_keys($jobs);
85
        $question = array();
86
87
        foreach ($jobs as $key => $job) {
88
            $question[] = '<comment>[' . ($index++) . ']</comment> ' . $job['Job'] . PHP_EOL;
89
        }
90
        $question[] = '<question>Please select job: </question>' . PHP_EOL;
91
92
        /** @var $dialogHelper DialogHelper */
93
        $dialogHelper = $this->getHelper('dialog');
94
        $jobCode = $dialogHelper->askAndValidate(
95
            $output,
96
            $question,
97
            function ($typeInput) use ($keyMap, $jobs) {
98
                $key = $keyMap[$typeInput];
99
                if (!isset($jobs[$key])) {
100
                    throw new InvalidArgumentException('Invalid job');
101
                }
102
103
                return $jobs[$key]['Job'];
104
            }
105
        );
106
107
        return $jobCode;
108
    }
109
110
    /**
111
     * @param string $runConfigModel
112
     * @param string $jobCode
113
     * @return array
114
     */
115
    private function getCallbackFromRunConfigModel($runConfigModel, $jobCode)
116
    {
117
        if (!preg_match(self::REGEX_RUN_MODEL, $runConfigModel, $runMatches)) {
118
            throw new RuntimeException(
119
                sprintf(
120
                    'Invalid model/method definition "%s" for job "%s", expecting "model/class::method".',
121
                    $runConfigModel,
122
                    $jobCode
123
                )
124
            );
125
        }
126
        list(, $runModel, $runMethod) = $runMatches;
127
        unset($runMatches);
128
129
        $model = Mage::getModel($runModel);
130
        if (false === $model) {
131
            throw new RuntimeException(sprintf('Failed to create new "%s" model for job "%s"', $runModel, $jobCode));
132
        }
133
        $callback = array($model, $runMethod);
134
        $callableName = sprintf("%s::%s", $runModel, $runMethod);
135
        if (!$model || !is_callable($callback, false, $callableName)) {
136
            throw new RuntimeException(sprintf('Invalid callback: %s for job "%s"', $callableName, $jobCode));
137
        }
138
139
        return array($callback, $callableName);
140
    }
141
142
    /**
143
     * @param array $callback
144
     * @param string $jobCode
145
     */
146
    private function executeConfigModel($callback, $jobCode)
147
    {
148
        Mage::getConfig()->init()->loadEventObservers('crontab');
149
        Mage::app()->addEventArea('crontab');
150
151
        /* @var $schedule Mage_Cron_Model_Schedule */
152
        $schedule = Mage::getModel('cron/schedule');
153
        if (false === $schedule) {
154
            throw new RuntimeException('Failed to create new Mage_Cron_Model_Schedule model');
155
        }
156
157
        $environment = new ServerEnvironment();
158
        $environment->initalize();
159
160
        try {
161
            $timestamp = strftime('%Y-%m-%d %H:%M:%S', time());
162
            $schedule
163
                ->setJobCode($jobCode)
164
                ->setStatus(Mage_Cron_Model_Schedule::STATUS_RUNNING)
165
                ->setCreatedAt($timestamp)
166
                ->setExecutedAt($timestamp)
167
                ->save();
168
169
            call_user_func_array($callback, array($schedule));
170
171
            $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS);
172
        } catch (Exception $cronException) {
173
            $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_ERROR);
174
        }
175
176
        $schedule->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))->save();
177
178
        if (isset($cronException)) {
179
            throw new RuntimeException(
180
                sprintf('Cron-job "%s" threw exception %s', $jobCode, get_class($cronException)),
181
                0,
182
                $cronException
183
            );
184
        }
185
186
        if (empty($callback)) {
187
            Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
188
        }
189
    }
190
191
    /**
192
     * @param array $callback
193
     * @param string $jobCode
194
     */
195
    private function scheduleConfigModel($callback, $jobCode)
196
    {
197
        /* @var $schedule Mage_Cron_Model_Schedule */
198
        $schedule = Mage::getModel('cron/schedule');
199
        if (false === $schedule) {
200
            throw new RuntimeException('Failed to create new Mage_Cron_Model_Schedule model');
201
        }
202
203
        if (empty($callback)) {
204
            Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
205
        }
206
207
        try {
208
            $timestamp = strftime('%Y-%m-%d %H:%M:%S', time());
209
            $schedule
210
                ->setJobCode($jobCode)
211
                ->setStatus(Mage_Cron_Model_Schedule::STATUS_PENDING)
212
                ->setCreatedAt($timestamp)
213
                ->setScheduledAt($timestamp)
214
                ->save();
215
        } catch (Exception $cronException) {
216
            throw new RuntimeException(
217
                sprintf('Cron-job "%s" threw exception %s', $jobCode, get_class($cronException)),
218
                0,
219
                $cronException
220
            );
221
        }
222
    }
223
224
    /**
225
     * @param $jobCode
226
     * @return string
227
     */
228
    private function getRunConfigModelByJobCode($jobCode)
229
    {
230
        $jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
231
        $defaultJobsRoot = Mage::getConfig()->getNode('default/crontab/jobs');
232
233
        /* @var $jobConfig Mage_Core_Model_Config_Element */
234
        $jobConfig = $jobsRoot->{$jobCode};
235
        if (!$jobConfig || !$jobConfig->run) {
236
            $jobConfig = $defaultJobsRoot->{$jobCode};
237
        }
238
        if (!$jobConfig || !$jobConfig->run) {
239
            throw new RuntimeException(sprintf('No job-config found for job "%s"!', $jobCode));
240
        }
241
242
        /* @var $runConfig Mage_Core_Model_Config_Element */
243
        $runConfig = $jobConfig->run;
244
        if (empty($runConfig->model)) {
245
            throw new RuntimeException(sprintf('No run-config found for job "%s"!', $jobCode));
246
        }
247
248
        $runConfigModel = (string) $runConfig->model;
249
250
        return $runConfigModel;
251
    }
252
}
253