Passed
Push — master ( 470f50...6685b9 )
by Ioannes
01:53
created

Cron::execute()   D

Complexity

Conditions 16
Paths 234

Size

Total Lines 106
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 57
c 4
b 1
f 0
dl 0
loc 106
rs 4.3083
cc 16
nc 234
nop 2

How to fix   Long Method    Complexity   

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\Output\OutputInterface;
9
use App\BxConsole\Annotations\Agent;
10
11
class Cron extends BxCommand {
12
13
    use LockableTrait;
14
15
    const EXEC_STATUS_SUCCESS = 'SUCCESS';
16
    const EXEC_STATUS_ERROR = 'ERROR';
17
    const EXEC_STATUS_WORK = 'WORK';
18
19
    private $minAgentPeriod;
20
21
    protected function configure() {
22
23
        $this->setName('system:cron')
24
            ->setDescription('Job sheduler for application comands');
25
    }
26
27
    protected function execute(InputInterface $input, OutputInterface $output)
28
    {
29
        $logger = EnvHelper::getLogger('bx_cron');
30
        if($logger) {
31
            $this->setLogger($logger);
32
        }
33
34
        if(EnvHelper::getSwitch('BX_CRONTAB_RUN', EnvHelper::SWITCH_STATE_OFF)) {
35
            if($this->logger) {
36
                $this->logger->alert('BxCron switch off');
37
            }
38
            return 0;
39
        }
40
41
        if(!$this->lock()) {
42
            $msg = 'The command is already running in another process.';
43
            $output->writeln($msg);
44
            if($this->logger) {
45
                $this->logger->warning($msg);
46
            }
47
            return 0;
48
        }
49
50
        if($sleepInterval = EnvHelper::checkSleepInterval()) {
51
            $msg = sprintf("Sleep in interval %s", $sleepInterval);
52
            $output->writeln($msg);
53
            if($this->logger) {
54
                $this->logger->warning($msg);
55
            }
56
            return 0;
57
        }
58
59
        $jobs = $this->getCronJobs();
60
61
        /*
62
         * Минимально допустимый период выполнения одной задачи
63
         * при котором гарантируется выполнение всех задач
64
         */
65
        $this->minAgentPeriod = (count($jobs) + 1) * EnvHelper::getBxCrontabPeriod();
66
67
        if(!empty($jobs)) {
68
69
            foreach($jobs as $cmd => $job) {
70
71
                if($this->isActualJob($job)) {
72
73
                    $job['status'] = self::EXEC_STATUS_WORK;
74
                    $this->updaateJob($cmd, $job);
75
76
                    $command = $this->getApplication()->find($cmd);
77
                    $cmdInput = new ArrayInput(['command' => $cmd]);
78
                    try {
79
80
                        $timeStart = microtime(true);
81
                        $returnCode = $command->run($cmdInput, $output);
82
83
                        if(!$returnCode) {
84
85
                            $job['status'] = self::EXEC_STATUS_SUCCESS;
86
87
                            $msg = sprintf("%s: SUCCESS [%.2f s]", $cmd, microtime(true) - $timeStart);
88
                            if($this->logger) {
89
                                $this->logger->alert($msg);
90
                            }
91
                            $output->writeln(PHP_EOL . $msg);
92
93
                        } else {
94
95
                            $job['status'] = self::EXEC_STATUS_ERROR;
96
                            $job['error_code'] = $returnCode;
97
98
                            $msg = sprintf("%s: ERROR [%.2f s]", $cmd, microtime(true) - $timeStart);
99
                            if($this->logger) {
100
                                $this->logger->alert($msg);
101
                            }
102
                            $output->writeln(PHP_EOL . $msg);
103
                        }
104
105
                    } catch (\Exception $e) {
106
107
                        $job['status'] = self::EXEC_STATUS_ERROR;
108
                        $job['error'] = $e->getMessage();
109
110
111
                        if($this->logger) {
112
                            $this->logger->error($e, ['command' => $cmd]);
113
                        }
114
                        $output->writeln(PHP_EOL . 'ERROR: ' . $e->getMessage());
115
116
                    } finally {
117
118
                        $job['last_exec'] = time();
119
                        $humanDate = new DateTime();
120
                        $job['last_date_time'] = $humanDate->toString();
121
                    }
122
123
                    $this->updaateJob($cmd, $job);
124
                    /*
125
                     * Let's do just one task
126
                     */
127
                    break;
128
                }
129
            } // foreach($jobs as $cmd => $job)
130
        } // if(!empty($jobs))
131
132
        $this->release();
133
    }
134
135
    protected function isActualJob(&$job) {
136
137
        if(isset($job['status']) && $job['status'] !== self::EXEC_STATUS_SUCCESS) {
138
            return false;
139
        }
140
141
        $period = intval($job['period']);
142
143
        if($period > 0) {
144
            if($period < $this->minAgentPeriod) {
145
                $job['orig_period'] = $period;
146
                $period = $job['period'] = $this->minAgentPeriod;
147
            }
148
            if(time() - $job['last_exec'] >= $period) {
149
                return true;
150
            }
151
        } else if(!empty($job['times'])) {
152
            //TODO:
153
        }
154
155
        return false;
156
    }
157
158
    protected function getCronJobs() {
159
160
        /** @var Application $app */
161
        $app = $this->getApplication();
162
163
        $commands = $app->all();
164
165
        $selfCommands = [];
166
        foreach($commands as $command) {
167
            /** @var BxCommand $command */
168
            if($command instanceof BxCommand) {
169
                $name = $command->getName();
170
                $selfCommands[$name] = [
171
                    'object' => $command,
172
                ];
173
            }
174
        }
175
176
        $agents = [];
177
        $reader = new AnnotationReader();
178
        foreach($selfCommands as $cmd => $selfCommand) {
179
            $reflectionClass = new \ReflectionClass($selfCommand['object']);
180
            $annotations = $reader->getClassAnnotations($reflectionClass);
181
182
            foreach($annotations as $annotation) {
183
                if($annotation instanceof Agent) {
184
                    $agents[$cmd] = $annotation->toArray();
185
                }
186
            }
187
        }
188
189
        $crontab = $this->getCronTab();
190
191
        if(is_array($crontab)) {
192
            foreach($crontab as $cmd => $job) {
193
                if(is_array($job) && isset($agents[$cmd])) {
194
                    $agents[$cmd] = array_merge($job, $agents[$cmd]);
195
                }
196
            }
197
        }
198
199
        $this->setCronTab($agents);
200
201
        return $agents;
202
    }
203
204
    protected function updaateJob($cmd, $job) {
205
206
        $this->updateCronTab([$cmd => $job]);
207
    }
208
209
    protected function updateCronTab(array $changedAgents) {
210
211
        $crontab = $this->getCronTab();
212
213
        $crontab = array_merge($crontab, $changedAgents);
0 ignored issues
show
Bug introduced by
It seems like $crontab can also be of type null; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
        $crontab = array_merge(/** @scrutinizer ignore-type */ $crontab, $changedAgents);
Loading history...
214
215
        $this->setCronTab($crontab);
216
    }
217
218
219
    protected function setCronTab(array $agents) {
220
221
        $filename = EnvHelper::getCrontabFile();
222
223
        $fh = fopen($filename, 'c');
224
        if (flock($fh, LOCK_EX)) {
225
            ftruncate($fh, 0);
226
            if(!fwrite($fh, json_encode($agents, JSON_PRETTY_PRINT))) {
227
                throw new \Exception('Unable to write BX_CRONTAB : ' . $filename);
228
            }
229
        }
230
        flock($fh, LOCK_UN);
231
        fclose($fh);
232
    }
233
234
    /**
235
     * @return mixed|null
236
     */
237
    protected function getCronTab() {
238
239
        $cronTab = null;
240
241
        $filename = EnvHelper::getCrontabFile();
242
243
        $fh = fopen($filename, 'r');
244
        if(flock($fh, LOCK_SH)) {
245
            $data = @fread($fh, filesize($filename));
246
            $cronTab = json_decode($data, true);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

246
            $cronTab = json_decode(/** @scrutinizer ignore-type */ $data, true);
Loading history...
247
        }
248
        flock($fh, LOCK_UN);
249
        fclose($fh);
250
251
        return $cronTab;
252
    }
253
}