Passed
Push — master ( f740a0...4a6c9d )
by Roman
02:16
created

JobMonitor::fillPushEnv()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 9
nc 4
nop 2
1
<?php
2
/**
3
 * @link https://github.com/zhuravljov/yii2-queue-monitor
4
 * @copyright Copyright (c) 2017 Roman Zhuravlev
5
 * @license http://opensource.org/licenses/BSD-3-Clause
6
 */
7
8
namespace zhuravljov\yii\queue\monitor;
9
10
use Yii;
11
use yii\base\Behavior;
12
use yii\base\InvalidConfigException;
13
use yii\queue\ErrorEvent;
14
use yii\queue\ExecEvent;
15
use yii\queue\JobEvent;
16
use yii\queue\PushEvent;
17
use yii\queue\Queue;
18
use zhuravljov\yii\queue\monitor\records\ExecRecord;
19
use zhuravljov\yii\queue\monitor\records\PushRecord;
20
use zhuravljov\yii\queue\monitor\records\WorkerRecord;
21
22
/**
23
 * Queue Job Monitor
24
 *
25
 * @author Roman Zhuravlev <[email protected]>
26
 */
27
class JobMonitor extends Behavior
28
{
29
    /**
30
     * @var Queue
31
     * @inheritdoc
32
     */
33
    public $owner;
34
    /**
35
     * @var bool|callable
36
     */
37
    public $storePushTrace = true;
38
    /**
39
     * @var bool|callable
40
     */
41
    public $storePushEnv = true;
42
    /**
43
     * @var Env
44
     */
45
    protected $env;
46
    /**
47
     * @var null|PushRecord
48
     */
49
    protected static $startedPush;
50
51
    /**
52
     * @param Env $env
53
     * @param array $config
54
     */
55
    public function __construct(Env $env, $config = [])
56
    {
57
        $this->env = $env;
58
        parent::__construct($config);
59
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64
    public function events()
65
    {
66
        return [
67
            Queue::EVENT_AFTER_PUSH => 'afterPush',
68
            Queue::EVENT_BEFORE_EXEC => 'beforeExec',
69
            Queue::EVENT_AFTER_EXEC => 'afterExec',
70
            Queue::EVENT_AFTER_ERROR => 'afterError',
71
        ];
72
    }
73
74
    /**
75
     * @param PushEvent $event
76
     */
77
    public function afterPush(PushEvent $event)
78
    {
79
        $push = new PushRecord();
80
        $push->parent_id = static::$startedPush ? static::$startedPush->id : null;
81
        $push->sender_name = $this->getSenderName($event);
82
        $push->job_uid = $event->id;
83
        $push->setJob($event->job);
84
        $push->push_ttr = $event->ttr;
85
        $push->push_delay = $event->delay;
86
        $this->fillPushTrace($push, $event);
87
        $this->fillPushEnv($push, $event);
88
        $push->pushed_at = time();
89
        $push->save(false);
90
    }
91
92
    /**
93
     * @param ExecEvent $event
94
     */
95
    public function beforeExec(ExecEvent $event)
96
    {
97
        static::$startedPush = $push = $this->getPushRecord($event);
98
        if (!$push) {
99
            return;
100
        }
101
        if ($push->isStopped()) {
102
            // Rejects job execution in case is stopped
103
            $event->handled = true;
104
            return;
105
        }
106
        $this->env->db->transaction(function () use ($event, $push) {
107
            $worker = $this->getWorkerRecord($event);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $worker is correct as $this->getWorkerRecord($event) targeting zhuravljov\yii\queue\mon...itor::getWorkerRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
108
109
            $exec = new ExecRecord();
110
            $exec->push_id = $push->id;
111
            if ($worker) {
112
                $exec->worker_id = $worker->id;
113
            }
114
            $exec->attempt = $event->attempt;
115
            $exec->reserved_at = time();
116
            $exec->save(false);
117
118
            $push->first_exec_id = $push->first_exec_id ?: $exec->id;
119
            $push->last_exec_id = $exec->id;
120
            $push->save(false);
121
122
            if ($worker) {
123
                $worker->last_exec_id = $exec->id;
124
                $worker->save(false);
125
            }
126
        });
127
    }
128
129
    /**
130
     * @param ExecEvent $event
131
     */
132
    public function afterExec(ExecEvent $event)
133
    {
134
        $push = static::$startedPush ?: $this->getPushRecord($event);
135
        if (!$push) {
136
            return;
137
        }
138
        if ($push->last_exec_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $push->last_exec_id of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
139
            ExecRecord::updateAll([
140
                'done_at' => time(),
141
                'error' => null,
142
                'retry' => false,
143
            ], [
144
                'id' => $push->last_exec_id
145
            ]);
146
        }
147
    }
148
149
    /**
150
     * @param ErrorEvent $event
151
     */
152
    public function afterError(ErrorEvent $event)
153
    {
154
        $push = static::$startedPush ?: $this->getPushRecord($event);
155
        if (!$push) {
156
            return;
157
        }
158
        if ($push->isStopped()) {
159
            // Breaks retry in case is stopped
160
            $event->retry = false;
161
        }
162
        if ($push->last_exec_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $push->last_exec_id of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
163
            ExecRecord::updateAll([
164
                'done_at' => time(),
165
                'error' => $event->error,
166
                'retry' => $event->retry,
167
            ], [
168
                'id' => $push->last_exec_id
169
            ]);
170
        }
171
    }
172
173
    /**
174
     * @param JobEvent $event
175
     * @throws
176
     * @return string
177
     */
178
    protected function getSenderName($event)
179
    {
180
        foreach (Yii::$app->getComponents(false) as $id => $component) {
181
            if ($component === $event->sender) {
182
                return $id;
183
            }
184
        }
185
        throw new InvalidConfigException('Queue must be an application component.');
186
    }
187
188
    /**
189
     * @param PushRecord $record
190
     * @param PushEvent $event
191
     */
192
    protected function fillPushTrace(PushRecord $record, PushEvent $event)
193
    {
194
        $record->push_trace_data = null;
195
196
        $canStore = is_callable($this->storePushTrace)
197
            ? call_user_func($this->storePushTrace, $event)
0 ignored issues
show
Bug introduced by
It seems like $this->storePushTrace can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, 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

197
            ? call_user_func(/** @scrutinizer ignore-type */ $this->storePushTrace, $event)
Loading history...
198
            : $this->storePushTrace;
199
        if (!$canStore) {
200
            return;
201
        }
202
203
        $record->push_trace_data = (new \Exception())->getTraceAsString();
204
    }
205
206
    /**
207
     * @param PushRecord $record
208
     * @param PushEvent $event
209
     */
210
    protected function fillPushEnv(PushRecord $record, PushEvent $event)
211
    {
212
        $record->push_env_data = null;
213
214
        $canStore = is_callable($this->storePushEnv)
215
            ? call_user_func($this->storePushEnv, $event)
0 ignored issues
show
Bug introduced by
It seems like $this->storePushEnv can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, 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

215
            ? call_user_func(/** @scrutinizer ignore-type */ $this->storePushEnv, $event)
Loading history...
216
            : $this->storePushEnv;
217
        if (!$canStore) {
218
            return;
219
        }
220
221
        $values = $_SERVER;
222
        ksort($values);
223
        $record->push_env_data = serialize($values);
224
    }
225
226
    /**
227
     * @param JobEvent $event
228
     * @return PushRecord
229
     */
230
    protected function getPushRecord(JobEvent $event)
231
    {
232
        if ($event->id !== null) {
233
            return $this->env->db->useMaster(function () use ($event) {
234
                return PushRecord::find()
235
                    ->byJob($this->getSenderName($event), $event->id)
236
                    ->one();
237
            });
238
        } else {
239
            return null;
240
        }
241
    }
242
243
    /**
244
     * @param ExecEvent $event
245
     * @return WorkerRecord|null
246
     */
247
    protected function getWorkerRecord(ExecEvent $event)
248
    {
249
        if ($event->sender->getWorkerPid() === null) {
0 ignored issues
show
introduced by
The condition $event->sender->getWorkerPid() === null is always true.
Loading history...
Bug introduced by
Are you sure the usage of $event->sender->getWorkerPid() targeting yii\queue\Queue::getWorkerPid() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
250
            return null;
251
        }
252
        if (!$this->isWorkerMonitored()) {
253
            return null;
254
        }
255
256
        return $this->env->db->useMaster(function () use ($event) {
257
            return WorkerRecord::find()
258
                ->byPid($event->sender->getWorkerPid())
259
                ->active()
260
                ->one();
261
        });
262
    }
263
264
    /**
265
     * @return bool whether workers are monitored.
266
     */
267
    private function isWorkerMonitored()
268
    {
269
        foreach ($this->owner->getBehaviors() as $behavior) {
270
            if ($behavior instanceof WorkerMonitor) {
271
                return true;
272
            }
273
        }
274
        return false;
275
    }
276
}
277