Completed
Pull Request — master (#966)
by
unknown
03:43
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', 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
HELP;
36
        $this->setHelp($help);
37
    }
38
39
    /**
40
     * @param InputInterface $input
41
     * @param OutputInterface $output
42
     *
43
     * @return int|void
44
     */
45
    protected function execute(InputInterface $input, OutputInterface $output)
46
    {
47
        $this->detectMagento($output, true);
48
        if (!$this->initMagento()) {
49
            return;
50
        }
51
52
        $jobCode = $input->getArgument('job');
53
        if (!$jobCode) {
54
            $this->writeSection($output, 'Cronjob');
55
            $jobCode = $this->askJobCode($output, $this->getJobs());
56
        }
57
58
        $runConfigModel = $this->getRunConfigModelByJobCode($jobCode);
59
60
        list($callback, $callableName) = $this->getCallbackFromRunConfigModel($runConfigModel, $jobCode);
61
62
        $output->write('<info>Run </info><comment>' . $callableName . '</comment> ');
63
64
        if (!$input->hasOption('schedule')) {
65
            $this->executeConfigModel($callback, $jobCode);
66
        } else {
67
            $this->scheduleConfigModel($callback, $jobCode);
68
        }
69
70
        $output->writeln('<info>done</info>');
71
    }
72
73
    /**
74
     * @param OutputInterface $output
75
     * @param array $jobs array of array containing "job" keyed string entries of job-codes
76
     *
77
     * @return string         job-code
78
     * @throws InvalidArgumentException when user selects invalid job interactively
79
     */
80
    protected function askJobCode(OutputInterface $output, array $jobs)
81
    {
82
        $index = 0;
83
        $keyMap = array_keys($jobs);
84
        $question = array();
85
86
        foreach ($jobs as $key => $job) {
87
            $question[] = '<comment>[' . ($index++) . ']</comment> ' . $job['Job'] . PHP_EOL;
88
        }
89
        $question[] = '<question>Please select job: </question>' . PHP_EOL;
90
91
        /** @var $dialogHelper DialogHelper */
92
        $dialogHelper = $this->getHelper('dialog');
93
        $jobCode = $dialogHelper->askAndValidate(
94
            $output,
95
            $question,
96
            function ($typeInput) use ($keyMap, $jobs) {
97
                $key = $keyMap[$typeInput];
98
                if (!isset($jobs[$key])) {
99
                    throw new InvalidArgumentException('Invalid job');
100
                }
101
102
                return $jobs[$key]['Job'];
103
            }
104
        );
105
106
        return $jobCode;
107
    }
108
109
    /**
110
     * @param string $runConfigModel
111
     * @param string $jobCode
112
     * @return array
113
     */
114
    private function getCallbackFromRunConfigModel($runConfigModel, $jobCode)
115
    {
116
        if (!preg_match(self::REGEX_RUN_MODEL, $runConfigModel, $runMatches)) {
117
            throw new RuntimeException(
118
                sprintf(
119
                    'Invalid model/method definition "%s" for job "%s", expecting "model/class::method".',
120
                    $runConfigModel,
121
                    $jobCode
122
                )
123
            );
124
        }
125
        list(, $runModel, $runMethod) = $runMatches;
126
        unset($runMatches);
127
128
        $model = Mage::getModel($runModel);
129
        if (false === $model) {
130
            throw new RuntimeException(sprintf('Failed to create new "%s" model for job "%s"', $runModel, $jobCode));
131
        }
132
        $callback = array($model, $runMethod);
133
        $callableName = sprintf("%s::%s", $runModel, $runMethod);
134
        if (!$model || !is_callable($callback, false, $callableName)) {
135
            throw new RuntimeException(sprintf('Invalid callback: %s for job "%s"', $callableName, $jobCode));
136
        }
137
138
        return array($callback, $callableName);
139
    }
140
141
    /**
142
     * @param array $callback
143
     * @param string $jobCode
144
     */
145
    private function executeConfigModel($callback, $jobCode)
146
    {
147
        Mage::getConfig()->init()->loadEventObservers('crontab');
148
        Mage::app()->addEventArea('crontab');
149
150
        /* @var $schedule Mage_Cron_Model_Schedule */
151
        $schedule = Mage::getModel('cron/schedule');
152
        if (false === $schedule) {
153
            throw new RuntimeException('Failed to create new Mage_Cron_Model_Schedule model');
154
        }
155
156
        $environment = new ServerEnvironment();
157
        $environment->initalize();
158
159
        try {
160
            $timestamp = strftime('%Y-%m-%d %H:%M:%S', time());
161
            $schedule
162
                ->setJobCode($jobCode)
163
                ->setStatus(Mage_Cron_Model_Schedule::STATUS_RUNNING)
164
                ->setCreatedAt($timestamp)
165
                ->setExecutedAt($timestamp)
166
                ->save();
167
168
            call_user_func_array($callback, array($schedule));
169
170
            $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS);
171
        } catch (Exception $cronException) {
172
            $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_ERROR);
173
        }
174
175
        $schedule->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))->save();
176
177
        if (isset($cronException)) {
178
            throw new RuntimeException(
179
                sprintf('Cron-job "%s" threw exception %s', $jobCode, get_class($cronException)),
180
                0,
181
                $cronException
182
            );
183
        }
184
185
        if (empty($callback)) {
186
            Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
187
        }
188
    }
189
190
    /**
191
     * @param array $callback
192
     * @param string $jobCode
193
     */
194
    private function scheduleConfigModel($callback, $jobCode)
195
    {
196
        /* @var $schedule Mage_Cron_Model_Schedule */
197
        $schedule = Mage::getModel('cron/schedule');
198
        if (false === $schedule) {
199
            throw new RuntimeException('Failed to create new Mage_Cron_Model_Schedule model');
200
        }
201
202
        if (empty($callback)) {
203
            Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
204
        }
205
206
        try {
207
            $timestamp = strftime('%Y-%m-%d %H:%M:%S', time());
208
            $schedule
209
                ->setJobCode($jobCode)
210
                ->setStatus(Mage_Cron_Model_Schedule::STATUS_PENDING)
211
                ->setCreatedAt($timestamp)
212
                ->setScheduledAt($timestamp)
213
                ->save();
214
        } catch (Exception $cronException) {
215
            throw new RuntimeException(
216
                sprintf('Cron-job "%s" threw exception %s', $jobCode, get_class($cronException)),
217
                0,
218
                $cronException
219
            );
220
        }
221
    }
222
223
    /**
224
     * @param $jobCode
225
     * @return string
226
     */
227
    private function getRunConfigModelByJobCode($jobCode)
228
    {
229
        $jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
230
        $defaultJobsRoot = Mage::getConfig()->getNode('default/crontab/jobs');
231
232
        /* @var $jobConfig Mage_Core_Model_Config_Element */
233
        $jobConfig = $jobsRoot->{$jobCode};
234
        if (!$jobConfig || !$jobConfig->run) {
235
            $jobConfig = $defaultJobsRoot->{$jobCode};
236
        }
237
        if (!$jobConfig || !$jobConfig->run) {
238
            throw new RuntimeException(sprintf('No job-config found for job "%s"!', $jobCode));
239
        }
240
241
        /* @var $runConfig Mage_Core_Model_Config_Element */
242
        $runConfig = $jobConfig->run;
243
        if (empty($runConfig->model)) {
244
            throw new RuntimeException(sprintf('No run-config found for job "%s"!', $jobCode));
245
        }
246
247
        $runConfigModel = (string) $runConfig->model;
248
249
        return $runConfigModel;
250
    }
251
}
252