Passed
Push — master ( e0e6c6...849d03 )
by Roman
02:16
created

PushRecord::getJob()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 5
nc 3
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A PushRecord::isSenderValid() 0 3 1
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\records;
9
10
use Yii;
11
use yii\db\ActiveRecord;
12
use yii\helpers\Json;
13
use yii\queue\JobInterface;
14
use yii\queue\Queue;
15
use zhuravljov\yii\queue\monitor\Env;
16
17
/**
18
 * Class PushRecord
19
 *
20
 * @property int $id
21
 * @property null|int $parent_id
22
 * @property string $sender_name
23
 * @property string $job_uid
24
 * @property string $job_class
25
 * @property string|resource $job_data
26
 * @property int $push_ttr
27
 * @property int $push_delay
28
 * @property string|null $push_trace_data
29
 * @property string|null $push_env_data
30
 * @property int $pushed_at
31
 * @property int|null $stopped_at
32
 * @property int|null $first_exec_id
33
 * @property int|null $last_exec_id
34
 *
35
 * @property PushRecord $parent
36
 * @property ExecRecord[] $execs
37
 * @property ExecRecord|null $firstExec
38
 * @property ExecRecord|null $lastExec
39
 * @property array $execTotal
40
 *
41
 * @property int $attemptCount
42
 * @property int $waitTime
43
 * @property string $status
44
 *
45
 * @property Queue|null $sender
46
 * @property array $jobParams
47
 * @property string[] $pushTrace
48
 * @property array $pushEnv
49
 *
50
 * @author Roman Zhuravlev <[email protected]>
51
 */
52
class PushRecord extends ActiveRecord
53
{
54
    const STATUS_STOPPED = 'stopped';
55
    const STATUS_WAITING = 'waiting';
56
    const STATUS_STARTED = 'started';
57
    const STATUS_DONE = 'done';
58
    const STATUS_FAILED = 'failed';
59
    const STATUS_RESTARTED = 'restarted';
60
    const STATUS_BURIED = 'buried';
61
62
    /**
63
     * @inheritdoc
64
     * @return PushQuery the active query used by this AR class.
65
     */
66
    public static function find()
67
    {
68
        return Yii::createObject(PushQuery::class, [get_called_class()]);
69
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74
    public static function getDb()
75
    {
76
        return Yii::$container->get(Env::class)->db;
77
    }
78
79
    /**
80
     * @inheritdoc
81
     */
82
    public static function tableName()
83
    {
84
        return Yii::$container->get(Env::class)->pushTableName;
85
    }
86
87
    /**
88
     * @return PushQuery
89
     */
90
    public function getParent()
91
    {
92
        return $this->hasOne(static::class, ['id' => 'parent_id']);
93
    }
94
95
    /**
96
     * @return ExecQuery
97
     */
98
    public function getExecs()
99
    {
100
        return $this->hasMany(ExecRecord::class, ['push_id' => 'id']);
101
    }
102
103
    /**
104
     * @return ExecQuery
105
     */
106
    public function getFirstExec()
107
    {
108
        return $this->hasOne(ExecRecord::class, ['id' => 'first_exec_id']);
109
    }
110
111
    /**
112
     * @return ExecQuery
113
     */
114
    public function getLastExec()
115
    {
116
        return $this->hasOne(ExecRecord::class, ['id' => 'last_exec_id']);
117
    }
118
119
    /**
120
     * @return ExecQuery
121
     */
122
    public function getExecTotal()
123
    {
124
        return $this->hasOne(ExecRecord::class, ['push_id' => 'id'])
125
            ->select([
126
                'push_id',
127
                'attempts' => 'COUNT(*)',
128
                'errors' => 'COUNT(error)',
129
            ])
130
            ->groupBy('push_id')
131
            ->asArray();
132
    }
133
134
    /**
135
     * @return int number of attempts
136
     */
137
    public function getAttemptCount()
138
    {
139
        return $this->execTotal['attempts'] ?: 0;
140
    }
141
142
    /**
143
     * @return int waiting time from push till first execute
144
     */
145
    public function getWaitTime()
146
    {
147
        if ($this->firstExec) {
148
            return $this->firstExec->reserved_at - $this->pushed_at - $this->push_delay;
149
        }
150
        return time() - $this->pushed_at - $this->push_delay;
151
    }
152
153
    /**
154
     * @return string
155
     */
156
    public function getStatus()
157
    {
158
        if ($this->isStopped()) {
159
            return self::STATUS_STOPPED;
160
        }
161
        if (!$this->lastExec) {
162
            return self::STATUS_WAITING;
163
        }
164
        if (!$this->lastExec->isDone() && $this->lastExec->attempt == 1) {
165
            return self::STATUS_STARTED;
166
        }
167
        if ($this->lastExec->isDone() && !$this->lastExec->isFailed()) {
168
            return self::STATUS_DONE;
169
        }
170
        if ($this->lastExec->isDone() && $this->lastExec->retry) {
171
            return self::STATUS_FAILED;
172
        }
173
        if (!$this->lastExec->isDone()) {
174
            return self::STATUS_RESTARTED;
175
        }
176
        if ($this->lastExec->isDone() && !$this->lastExec->retry) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lastExec->retry of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
177
            return self::STATUS_BURIED;
178
        }
179
        return null;
180
    }
181
182
    /**
183
     * @return Queue|null
184
     */
185
    public function getSender()
186
    {
187
        return Yii::$app->get($this->sender_name, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Yii::app->get($this->sender_name, false) also could return the type mixed which is incompatible with the documented return type null|yii\queue\Queue.
Loading history...
188
    }
189
190
    /**
191
     * @return bool
192
     */
193
    public function isSenderValid()
194
    {
195
        return $this->getSender() instanceof Queue;
196
    }
197
198
    /**
199
     * @param JobInterface|mixed $job
200
     */
201
    public function setJob($job)
202
    {
203
        $this->job_class = get_class($job);
204
        $data = [];
205
        foreach (get_object_vars($job) as $name => $value) {
206
            $data[$name] = $this->serializeData($value);
207
        }
208
        $this->job_data = Json::encode($data);
209
    }
210
211
    /**
212
     * @return array of job properties
213
     */
214
    public function getJobParams()
215
    {
216
        if (is_resource($this->job_data)) {
217
            $this->job_data = stream_get_contents($this->job_data);
218
        }
219
        $params = [];
220
        foreach (Json::decode($this->job_data) as $name => $value) {
221
            $params[$name] = $this->unserializeData($value);
222
        }
223
        return $params;
224
    }
225
226
    /**
227
     * @return bool
228
     */
229
    public function isJobValid()
230
    {
231
        return is_subclass_of($this->job_class, JobInterface::class);
232
    }
233
234
    /**
235
     * @return JobInterface|mixed
236
     */
237
    public function createJob()
238
    {
239
        return Yii::createObject(['class' => $this->job_class] + $this->getJobParams());
240
    }
241
242
    /**
243
     * @return string[] trace lines since Queue::push()
244
     */
245
    public function getPushTrace()
246
    {
247
        if (is_resource($this->push_trace_data)) {
0 ignored issues
show
introduced by
The condition is_resource($this->push_trace_data) is always false.
Loading history...
248
            $this->push_trace_data = stream_get_contents($this->push_trace_data);
249
        }
250
        $lines = [];
251
        $isFirstFound = false;
252
        foreach (explode("\n", $this->push_trace_data) as $line) {
253
            if (!$isFirstFound && strpos($line, \yii\queue\Queue::class)) {
254
                $isFirstFound = true;
255
            }
256
            if ($isFirstFound) {
257
                list(, $line) = explode(' ', trim($line), 2);
258
                $lines[] = $line;
259
            }
260
        }
261
        return $lines;
262
    }
263
264
    /**
265
     * @param array $values
266
     */
267
    public function setPushEnv($values)
268
    {
269
        ksort($values);
270
        $this->push_env_data = serialize($values);
271
    }
272
273
    /**
274
     * @return array
275
     */
276
    public function getPushEnv()
277
    {
278
        if ($this->push_env_data === null) {
279
            return [];
280
        }
281
        if (is_resource($this->push_env_data)) {
0 ignored issues
show
introduced by
The condition is_resource($this->push_env_data) is always false.
Loading history...
282
            $this->push_env_data = stream_get_contents($this->push_env_data);
283
        }
284
        return unserialize($this->push_env_data);
285
    }
286
287
    /**
288
     * @return bool
289
     */
290
    public function canPushAgain()
291
    {
292
        return $this->isSenderValid() && $this->isJobValid();
293
    }
294
295
    /**
296
     * @return bool marked as stopped
297
     */
298
    public function isStopped()
299
    {
300
        return !!$this->stopped_at;
301
    }
302
303
    /**
304
     * @return bool ability to mark as stopped
305
     */
306
    public function canStop()
307
    {
308
        if ($this->isStopped()) {
309
            return false;
310
        }
311
        if ($this->lastExec && $this->lastExec->isDone() && !$this->lastExec->retry) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lastExec->retry of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
312
            return false;
313
        }
314
        return true;
315
    }
316
317
    /**
318
     * Marks as stopped
319
     */
320
    public function stop()
321
    {
322
        $this->stopped_at = time();
323
        $this->save(false);
324
    }
325
326
    /**
327
     * @param mixed $data
328
     * @return mixed
329
     */
330
    private function serializeData($data)
331
    {
332
        if (is_object($data)) {
333
            $result = ['=class=' => get_class($data)];
334
            foreach (get_object_vars($data) as $name => $value) {
335
                $result[$name] = $this->serializeData($value);
336
            }
337
            return $result;
338
        }
339
340
        if (is_array($data)) {
341
            $result = [];
342
            foreach ($data as $name => $value) {
343
                $result[$name] = $this->serializeData($value);
344
            }
345
        }
346
347
        return $data;
348
    }
349
350
    /**
351
     * @param mixed $data
352
     * @return mixed
353
     */
354
    private function unserializeData($data)
355
    {
356
        if (!is_array($data)) {
357
            return $data;
358
        }
359
360
        if (!isset($data['=class='])) {
361
            $result = [];
362
            foreach ($data as $key => $value) {
363
                $result[$key] = $this->unserializeData($value);
364
            }
365
            return $result;
366
        }
367
368
        $config = ['class' => $data['=class=']];
369
        unset($data['=class=']);
370
        foreach ($data as $property => $value) {
371
            $config[$property] = $this->unserializeData($value);
372
        }
373
        return Yii::createObject($config);
374
    }
375
}
376