Completed
Push — develop ( 2a6eb6...bba74c )
by Tom
02:34
created

RunCommand::scheduleConfigModel()   A

Complexity

Conditions 4
Paths 7

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 7
nop 2
dl 0
loc 28
rs 9.472
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
     * @throws Exception
46
     */
47
    protected function execute(InputInterface $input, OutputInterface $output)
48
    {
49
        $this->detectMagento($output, true);
50
        if (!$this->initMagento()) {
51
            return;
52
        }
53
54
        $jobCode = $input->getArgument('job');
55
        if (!$jobCode) {
56
            $this->writeSection($output, 'Cronjob');
57
            $jobCode = $this->askJobCode($output, $this->getJobs());
58
        }
59
60
        $runConfigModel = $this->getRunConfigModelByJobCode($jobCode);
61
62
        list($callback, $callableName) = $this->getCallbackFromRunConfigModel($runConfigModel, $jobCode);
0 ignored issues
show
Bug introduced by
It seems like $jobCode can also be of type array<integer,string>; however, N98\Magento\Command\Syst...ackFromRunConfigModel() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
63
64
        $output->write('<info>Run </info><comment>' . $callableName . '</comment> ');
65
66
        if ($input->hasOption('schedule') && $input->getOption('schedule')) {
67
            $this->scheduleConfigModel($callback, $jobCode);
0 ignored issues
show
Bug introduced by
It seems like $jobCode defined by $input->getArgument('job') on line 54 can also be of type array<integer,string>; however, N98\Magento\Command\Syst...::scheduleConfigModel() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
68
        } else {
69
            $this->executeConfigModel($callback, $jobCode);
0 ignored issues
show
Bug introduced by
It seems like $jobCode defined by $input->getArgument('job') on line 54 can also be of type array<integer,string>; however, N98\Magento\Command\Syst...d::executeConfigModel() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
70
        }
71
72
        $output->writeln('<info>done</info>');
73
    }
74
75
    /**
76
     * @param OutputInterface $output
77
     * @param array $jobs array of array containing "job" keyed string entries of job-codes
78
     *
79
     * @return string         job-code
80
     * @throws InvalidArgumentException|Exception when user selects invalid job interactively
81
     */
82
    protected function askJobCode(OutputInterface $output, array $jobs)
83
    {
84
        $index = 0;
85
        $keyMap = array_keys($jobs);
86
        $question = array();
87
88
        foreach ($jobs as $key => $job) {
89
            $question[] = '<comment>[' . ($index++) . ']</comment> ' . $job['Job'] . PHP_EOL;
90
        }
91
        $question[] = '<question>Please select job: </question>' . PHP_EOL;
92
93
        /** @var $dialogHelper DialogHelper */
94
        $dialogHelper = $this->getHelper('dialog');
95
96
        return $dialogHelper->askAndValidate(
97
            $output,
98
            $question,
99
            function ($typeInput) use ($keyMap, $jobs) {
100
                $key = $keyMap[$typeInput];
101
                if (!isset($jobs[$key])) {
102
                    throw new InvalidArgumentException('Invalid job');
103
                }
104
105
                return $jobs[$key]['Job'];
106
            }
107
        );
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
     * @throws Exception
146
     */
147
    private function executeConfigModel($callback, $jobCode)
148
    {
149
        Mage::getConfig()->init()->loadEventObservers('crontab');
150
        Mage::app()->addEventArea('crontab');
151
152
        /* @var $schedule Mage_Cron_Model_Schedule */
153
        $schedule = Mage::getModel('cron/schedule');
154
        if (false === $schedule) {
155
            throw new RuntimeException('Failed to create new Mage_Cron_Model_Schedule model');
156
        }
157
158
        $environment = new ServerEnvironment();
159
        $environment->initalize();
160
161
        try {
162
            $timestamp = strftime('%Y-%m-%d %H:%M:%S', time());
163
            $schedule
164
                ->setJobCode($jobCode)
165
                ->setStatus(Mage_Cron_Model_Schedule::STATUS_RUNNING)
166
                ->setCreatedAt($timestamp)
167
                ->setExecutedAt($timestamp)
168
                ->setScheduledAt($timestamp)
169
                ->save();
170
171
            $callback($schedule);
172
173
            $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS);
174
        } catch (Exception $cronException) {
175
            $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_ERROR);
176
        }
177
178
        $schedule->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()))->save();
179
180
        if (isset($cronException)) {
181
            throw new RuntimeException(
182
                sprintf('Cron-job "%s" threw exception %s', $jobCode, get_class($cronException)),
183
                0,
184
                $cronException
185
            );
186
        }
187
188
        if (empty($callback)) {
189
            Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
190
        }
191
    }
192
193
    /**
194
     * @param array $callback
195
     * @param string $jobCode
196
     */
197
    private function scheduleConfigModel($callback, $jobCode)
198
    {
199
        /* @var $schedule Mage_Cron_Model_Schedule */
200
        $schedule = Mage::getModel('cron/schedule');
201
        if (false === $schedule) {
202
            throw new RuntimeException('Failed to create new Mage_Cron_Model_Schedule model');
203
        }
204
205
        if (empty($callback)) {
206
            Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
207
        }
208
209
        try {
210
            $timestamp = strftime('%Y-%m-%d %H:%M:%S', time());
211
            $schedule
212
                ->setJobCode($jobCode)
213
                ->setStatus(Mage_Cron_Model_Schedule::STATUS_PENDING)
214
                ->setCreatedAt($timestamp)
215
                ->setScheduledAt($timestamp)
216
                ->save();
217
        } catch (Exception $cronException) {
218
            throw new RuntimeException(
219
                sprintf('Cron-job "%s" threw exception %s', $jobCode, get_class($cronException)),
220
                0,
221
                $cronException
222
            );
223
        }
224
    }
225
226
    /**
227
     * @param $jobCode
228
     * @return string
229
     */
230
    private function getRunConfigModelByJobCode($jobCode)
231
    {
232
        $jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
233
        $defaultJobsRoot = Mage::getConfig()->getNode('default/crontab/jobs');
234
235
        /* @var $jobConfig Mage_Core_Model_Config_Element */
236
        $jobConfig = $jobsRoot->{$jobCode};
237
        if (!$jobConfig || !$jobConfig->run) {
238
            $jobConfig = $defaultJobsRoot->{$jobCode};
239
        }
240
        if (!$jobConfig || !$jobConfig->run) {
241
            throw new RuntimeException(sprintf('No job-config found for job "%s"!', $jobCode));
242
        }
243
244
        /* @var $runConfig Mage_Core_Model_Config_Element */
245
        $runConfig = $jobConfig->run;
246
        if (empty($runConfig->model)) {
247
            throw new RuntimeException(sprintf('No run-config found for job "%s"!', $jobCode));
248
        }
249
250
        return (string) $runConfig->model;
251
    }
252
}
253