Completed
Push — master ( 880b7a...33b151 )
by Tom
09:34 queued 06:03
created

RunCommand::executeConfigModel()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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