Passed
Push — master ( ba342a...e49e3e )
by Ioannes
01:57
created

Cron::executeJobs()   B

Complexity

Conditions 9
Paths 111

Size

Total Lines 71
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 37
c 1
b 0
f 0
dl 0
loc 71
rs 7.6991
cc 9
nc 111
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace App\BxConsole;
3
4
use Bitrix\Main\Type\DateTime;
0 ignored issues
show
Bug introduced by
The type Bitrix\Main\Type\DateTime was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
5
use Doctrine\Common\Annotations\AnnotationReader;
6
use Symfony\Component\Console\Input\ArrayInput;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use App\BxConsole\Annotations\Agent;
11
12
class Cron extends BxCommand {
13
14
    use LockableTrait;
15
16
    const EXEC_STATUS_SUCCESS = 'SUCCESS';
17
    const EXEC_STATUS_ERROR = 'ERROR';
18
    const EXEC_STATUS_WORK = 'WORK';
19
20
    private $minAgentPeriod;
21
22
    protected function configure() {
23
24
        $this->setName('system:cron')
25
            ->setDescription('Job sheduler for application comands')
26
            ->addOption('clean', 'c', InputOption::VALUE_REQUIRED, 'Command to be clean crontab data (status, last exec)');
27
    }
28
29
    protected function execute(InputInterface $input, OutputInterface $output)
30
    {
31
        $logger = EnvHelper::getLogger('bx_cron');
32
        if($logger) {
33
            $this->setLogger($logger);
34
        }
35
36
        if(EnvHelper::getSwitch('BX_CRONTAB_RUN', EnvHelper::SWITCH_STATE_OFF)) {
37
            if($this->logger) {
38
                $this->logger->alert('BxCron switch off');
39
            }
40
            return 0;
41
        }
42
43
        if(!$this->lock()) {
44
            $msg = 'The command is already running in another process.';
45
            $output->writeln($msg);
46
            if($this->logger) {
47
                $this->logger->warning($msg);
48
            }
49
            return 0;
50
        }
51
52
        if($sleepInterval = EnvHelper::checkSleepInterval()) {
53
            $msg = sprintf("Sleep in interval %s", $sleepInterval);
54
            $output->writeln($msg);
55
            if($this->logger) {
56
                $this->logger->warning($msg);
57
            }
58
            return 0;
59
        }
60
61
        $clean = $input->getOption('clean');
62
        if($clean) {
63
            $command = $this->getApplication()->find($clean);
64
            $this->cleanJob($command->getName());
65
            $output->writeln($command->getName() . " will be executed now");
66
            return 0;
67
        }
68
69
        $this->executeJobs($output);
70
71
        $this->release();
72
    }
73
74
    protected function cleanJob($command) {
75
76
        $crontab = $this->getCronTab();
77
78
        unset($crontab[$command]);
79
80
        $this->setCronTab($crontab);
81
    }
82
83
    protected function executeJobs(OutputInterface $output) {
84
85
        $jobs = $this->getCronJobs();
86
87
        /*
88
         * Минимально допустимый период выполнения одной задачи
89
         * при котором гарантируется выполнение всех задач
90
         */
91
        $this->minAgentPeriod = (count($jobs) + 1) * EnvHelper::getBxCrontabPeriod();
92
93
        if(!empty($jobs)) {
94
95
            foreach($jobs as $cmd => $job) {
96
97
                if($this->isActualJob($job)) {
98
99
                    $job['status'] = self::EXEC_STATUS_WORK;
100
                    $this->updaateJob($cmd, $job);
101
102
                    $command = $this->getApplication()->find($cmd);
103
                    $cmdInput = new ArrayInput(['command' => $cmd]);
104
                    try {
105
106
                        $timeStart = microtime(true);
107
                        $returnCode = $command->run($cmdInput, $output);
108
109
                        if(!$returnCode) {
110
111
                            $job['status'] = self::EXEC_STATUS_SUCCESS;
112
113
                            $msg = sprintf("%s: SUCCESS [%.2f s]", $cmd, microtime(true) - $timeStart);
114
                            if($this->logger) {
115
                                $this->logger->alert($msg);
116
                            }
117
                            $output->writeln(PHP_EOL . $msg);
118
119
                        } else {
120
121
                            $job['status'] = self::EXEC_STATUS_ERROR;
122
                            $job['error_code'] = $returnCode;
123
124
                            $msg = sprintf("%s: ERROR [%.2f s]", $cmd, microtime(true) - $timeStart);
125
                            if($this->logger) {
126
                                $this->logger->alert($msg);
127
                            }
128
                            $output->writeln(PHP_EOL . $msg);
129
                        }
130
131
                    } catch (\Exception $e) {
132
133
                        $job['status'] = self::EXEC_STATUS_ERROR;
134
                        $job['error'] = $e->getMessage();
135
136
137
                        if($this->logger) {
138
                            $this->logger->error($e, ['command' => $cmd]);
139
                        }
140
                        $output->writeln(PHP_EOL . 'ERROR: ' . $e->getMessage());
141
142
                    } finally {
143
144
                        $job['last_exec'] = time();
145
                        $humanDate = new DateTime();
146
                        $job['last_date_time'] = $humanDate->toString();
147
                    }
148
149
                    $this->updaateJob($cmd, $job);
150
                    /*
151
                     * Let's do just one task
152
                     */
153
                    break;
154
                }
155
            } // foreach($jobs as $cmd => $job)
156
        } // if(!empty($jobs))
157
    }
158
159
    protected function isActualJob(&$job) {
160
161
        if(isset($job['status']) && $job['status'] !== self::EXEC_STATUS_SUCCESS) {
162
            return false;
163
        }
164
165
        $period = intval($job['period']);
166
167
        if($period > 0) {
168
            if($period < $this->minAgentPeriod) {
169
                $job['orig_period'] = $period;
170
                $period = $job['period'] = $this->minAgentPeriod;
171
            }
172
            if(time() - $job['last_exec'] >= $period) {
173
                return true;
174
            }
175
        } else if(!empty($job['times'])) {
176
            //TODO:
177
        }
178
179
        return false;
180
    }
181
182
    protected function getCronJobs() {
183
184
        /** @var Application $app */
185
        $app = $this->getApplication();
186
187
        $commands = $app->all();
188
189
        $selfCommands = [];
190
        foreach($commands as $command) {
191
            /** @var BxCommand $command */
192
            if($command instanceof BxCommand) {
193
                $name = $command->getName();
194
                $selfCommands[$name] = [
195
                    'object' => $command,
196
                ];
197
            }
198
        }
199
200
        $agents = [];
201
        $reader = new AnnotationReader();
202
        foreach($selfCommands as $cmd => $selfCommand) {
203
            $reflectionClass = new \ReflectionClass($selfCommand['object']);
204
            $annotations = $reader->getClassAnnotations($reflectionClass);
205
206
            foreach($annotations as $annotation) {
207
                if($annotation instanceof Agent) {
208
                    $agents[$cmd] = $annotation->toArray();
209
                }
210
            }
211
        }
212
213
        $crontab = $this->getCronTab();
214
215
        foreach($crontab as $cmd => $job) {
216
            if(is_array($job) && isset($agents[$cmd])) {
217
                $agents[$cmd] = array_merge($job, $agents[$cmd]);
218
            }
219
        }
220
221
        $this->setCronTab($agents);
222
223
        return $agents;
224
    }
225
226
    protected function updaateJob($cmd, $job) {
227
228
        $this->updateCronTab([$cmd => $job]);
229
    }
230
231
    protected function updateCronTab(array $changedAgents) {
232
233
        $crontab = $this->getCronTab();
234
235
        $crontab = array_merge($crontab, $changedAgents);
236
237
        $this->setCronTab($crontab);
238
    }
239
240
241
    protected function setCronTab(array $agents) {
242
243
        $filename = EnvHelper::getCrontabFile();
244
245
        $fh = fopen($filename, 'c');
246
        if (flock($fh, LOCK_EX)) {
247
            ftruncate($fh, 0);
248
            if(!fwrite($fh, json_encode($agents, JSON_PRETTY_PRINT))) {
249
                throw new \Exception('Unable to write BX_CRONTAB : ' . $filename);
250
            }
251
        }
252
        flock($fh, LOCK_UN);
253
        fclose($fh);
254
    }
255
256
    /**
257
     * @return array
258
     */
259
    protected function getCronTab() {
260
261
        $cronTab = [];
262
263
        $filename = EnvHelper::getCrontabFile();
264
265
        $fh = fopen($filename, 'r');
266
        if(flock($fh, LOCK_SH)) {
267
            if($data = @fread($fh, filesize($filename))) {
268
                $decoded = json_decode($data, true);
269
                if(is_array($decoded)) {
270
                    $cronTab = $decoded;
271
                }
272
            }
273
        }
274
        flock($fh, LOCK_UN);
275
        fclose($fh);
276
277
        return $cronTab;
278
    }
279
}