Passed
Push — master ( 97692f...8a9378 )
by Ioannes
01:59
created

Cron::execute()   C

Complexity

Conditions 14
Paths 230

Size

Total Lines 97
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 51
c 3
b 1
f 0
dl 0
loc 97
rs 5.0583
cc 14
nc 230
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
            $output->writeln("The command is already running in another process.");
43
            if($this->logger) {
44
                $this->logger->warning("The command is already running in another process.");
45
            }
46
            return 0;
47
        }
48
49
        $jobs = $this->getCronJobs();
50
51
        /*
52
         * Минимально допустимый период выполнения одной задачи
53
         * при котором гарантируется выполнение всех задач
54
         */
55
        $this->minAgentPeriod = (count($jobs) + 1) * EnvHelper::getBxCrontabPeriod();
0 ignored issues
show
Bug Best Practice introduced by
The method App\BxConsole\EnvHelper::getBxCrontabPeriod() is not static, but was called statically. ( Ignorable by Annotation )

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

55
        $this->minAgentPeriod = (count($jobs) + 1) * EnvHelper::/** @scrutinizer ignore-call */ getBxCrontabPeriod();
Loading history...
56
57
        if(!empty($jobs)) {
58
59
            foreach($jobs as $cmd => $job) {
60
61
                if($this->isActualJob($job)) {
62
63
                    $job['status'] = self::EXEC_STATUS_WORK;
64
                    $this->updaateJob($cmd, $job);
65
66
                    $command = $this->getApplication()->find($cmd);
67
                    $cmdInput = new ArrayInput(['command' => $cmd]);
68
                    try {
69
70
                        $timeStart = microtime(true);
71
                        $returnCode = $command->run($cmdInput, $output);
72
73
                        if(!$returnCode) {
74
75
                            $job['status'] = self::EXEC_STATUS_SUCCESS;
76
77
                            $msg = sprintf("%s: SUCCESS [%.2f s]", $cmd, microtime(true) - $timeStart);
78
                            if($this->logger) {
79
                                $this->logger->alert($msg);
80
                            }
81
                            $output->writeln(PHP_EOL . $msg);
82
83
                        } else {
84
85
                            $job['status'] = self::EXEC_STATUS_ERROR;
86
                            $job['error_code'] = $returnCode;
87
88
                            $msg = sprintf("%s: ERROR [%.2f s]", $cmd, microtime(true) - $timeStart);
89
                            if($this->logger) {
90
                                $this->logger->alert($msg);
91
                            }
92
                            $output->writeln(PHP_EOL . $msg);
93
                        }
94
95
                    } catch (\Exception $e) {
96
97
                        $job['status'] = self::EXEC_STATUS_ERROR;
98
                        $job['error'] = $e->getMessage();
99
100
101
                        if($this->logger) {
102
                            $this->logger->error($e, ['command' => $cmd]);
103
                        }
104
                        $output->writeln(PHP_EOL . 'ERROR: ' . $e->getMessage());
105
106
                    } finally {
107
108
                        $job['last_exec'] = time();
109
                        $humanDate = new DateTime();
110
                        $job['last_date_time'] = $humanDate->toString();
111
                        $lock->release();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lock seems to be never defined.
Loading history...
112
                    }
113
114
                    $this->updaateJob($cmd, $job);
115
                    /*
116
                     * Let's do just one task
117
                     */
118
                    break;
119
                }
120
            } // foreach($jobs as $cmd => $job)
121
        } // if(!empty($jobs))
122
123
        $this->release();
124
    }
125
126
    protected function isActualJob(&$job) {
127
128
        if(isset($job['status']) && $job['status'] !== self::EXEC_STATUS_SUCCESS) {
129
            return false;
130
        }
131
132
        $period = intval($job['period']);
133
134
        if($period > 0) {
135
            if($period < $this->minAgentPeriod) {
136
                $job['orig_period'] = $period;
137
                $period = $job['period'] = $this->minAgentPeriod;
138
            }
139
            if(time() - $job['last_exec'] >= $period) {
140
                return true;
141
            }
142
        } else if(!empty($job['times'])) {
143
            //TODO:
144
        }
145
146
        return false;
147
    }
148
149
    protected function getCronJobs() {
150
151
        /** @var Application $app */
152
        $app = $this->getApplication();
153
154
        $commands = $app->all();
155
156
        $selfCommands = [];
157
        foreach($commands as $command) {
158
            /** @var BxCommand $command */
159
            if($command instanceof BxCommand) {
160
                $name = $command->getName();
161
                $selfCommands[$name] = [
162
                    'object' => $command,
163
                ];
164
            }
165
        }
166
167
        $agents = [];
168
        $reader = new AnnotationReader();
169
        foreach($selfCommands as $cmd => $selfCommand) {
170
            $reflectionClass = new \ReflectionClass($selfCommand['object']);
171
            $annotations = $reader->getClassAnnotations($reflectionClass);
172
173
            foreach($annotations as $annotation) {
174
                if($annotation instanceof Agent) {
175
                    $agents[$cmd] = $annotation->toArray();
176
                }
177
            }
178
        }
179
180
        $crontab = $this->getCronTab();
181
182
        if(is_array($crontab)) {
183
            foreach($crontab as $cmd => $job) {
184
                if(is_array($job) && isset($agents[$cmd])) {
185
                    $agents[$cmd] = array_merge($job, $agents[$cmd]);
186
                }
187
            }
188
        }
189
190
        $this->setCronTab($agents);
191
192
        return $agents;
193
    }
194
195
    protected function updaateJob($cmd, $job) {
196
197
        $this->updateCronTab([$cmd => $job]);
198
    }
199
200
    protected function updateCronTab(array $changedAgents) {
201
202
        $crontab = $this->getCronTab();
203
204
        $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

204
        $crontab = array_merge(/** @scrutinizer ignore-type */ $crontab, $changedAgents);
Loading history...
205
206
        $this->setCronTab($crontab);
207
    }
208
209
210
    protected function setCronTab(array $agents) {
211
212
        $filename = EnvHelper::getCrontabFile();
213
214
        $fh = fopen($filename, 'c');
215
        if (flock($fh, LOCK_EX)) {
216
            ftruncate($fh, 0);
217
            if(!fwrite($fh, json_encode($agents, JSON_PRETTY_PRINT))) {
218
                throw new \Exception('Unable to write BX_CRONTAB : ' . $filename);
219
            }
220
        }
221
        flock($fh, LOCK_UN);
222
        fclose($fh);
223
    }
224
225
    /**
226
     * @return mixed|null
227
     */
228
    protected function getCronTab() {
229
230
        $cronTab = null;
231
232
        $filename = EnvHelper::getCrontabFile();
233
234
        $fh = fopen($filename, 'r');
235
        if(flock($fh, LOCK_SH)) {
236
            $data = @fread($fh, filesize($filename));
237
            $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

237
            $cronTab = json_decode(/** @scrutinizer ignore-type */ $data, true);
Loading history...
238
        }
239
        flock($fh, LOCK_UN);
240
        fclose($fh);
241
242
        return $cronTab;
243
    }
244
}