Passed
Push — master ( b313ea...47c9db )
by Roman
03:00
created

PushRecord::getPushTrace()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
c 0
b 0
f 0
rs 8.2222
cc 7
eloc 13
nc 11
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A PushRecord::isStopped() 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
 * Push Record
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 $ttr
27
 * @property int $delay
28
 * @property string|null $trace
29
 * @property string|null $context
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 PushRecord[] $children
37
 * @property ExecRecord[] $execs
38
 * @property ExecRecord|null $firstExec
39
 * @property ExecRecord|null $lastExec
40
 * @property array $execTotal
41
 *
42
 * @property int $attemptCount
43
 * @property int $waitTime
44
 * @property string $status
45
 *
46
 * @property Queue|null $sender
47
 * @property array $jobParams
48
 *
49
 * @author Roman Zhuravlev <[email protected]>
50
 */
51
class PushRecord extends ActiveRecord
52
{
53
    const STATUS_STOPPED = 'stopped';
54
    const STATUS_WAITING = 'waiting';
55
    const STATUS_STARTED = 'started';
56
    const STATUS_DONE = 'done';
57
    const STATUS_FAILED = 'failed';
58
    const STATUS_RESTARTED = 'restarted';
59
    const STATUS_BURIED = 'buried';
60
61
    /**
62
     * @inheritdoc
63
     * @return PushQuery the active query used by this AR class.
64
     */
65
    public static function find()
66
    {
67
        return Yii::createObject(PushQuery::class, [get_called_class()]);
68
    }
69
70
    /**
71
     * @inheritdoc
72
     */
73
    public static function getDb()
74
    {
75
        return Yii::$container->get(Env::class)->db;
76
    }
77
78
    /**
79
     * @inheritdoc
80
     */
81
    public static function tableName()
82
    {
83
        return Yii::$container->get(Env::class)->pushTableName;
84
    }
85
86
    /**
87
     * @return PushQuery
88
     */
89
    public function getParent()
90
    {
91
        return $this->hasOne(static::class, ['id' => 'parent_id']);
92
    }
93
94
    /**
95
     * @return PushQuery
96
     */
97
    public function getChildren()
98
    {
99
        return $this->hasMany(static::class, ['parent_id' => 'id']);
100
    }
101
102
    /**
103
     * @return ExecQuery
104
     */
105
    public function getExecs()
106
    {
107
        return $this->hasMany(ExecRecord::class, ['push_id' => 'id']);
108
    }
109
110
    /**
111
     * @return ExecQuery
112
     */
113
    public function getFirstExec()
114
    {
115
        return $this->hasOne(ExecRecord::class, ['id' => 'first_exec_id']);
116
    }
117
118
    /**
119
     * @return ExecQuery
120
     */
121
    public function getLastExec()
122
    {
123
        return $this->hasOne(ExecRecord::class, ['id' => 'last_exec_id']);
124
    }
125
126
    /**
127
     * @return ExecQuery
128
     */
129
    public function getExecTotal()
130
    {
131
        return $this->hasOne(ExecRecord::class, ['push_id' => 'id'])
132
            ->select([
133
                'exec.push_id',
134
                'attempts' => 'COUNT(*)',
135
                'errors' => 'COUNT(exec.error)',
136
            ])
137
            ->groupBy('exec.push_id')
138
            ->asArray();
139
    }
140
141
    /**
142
     * @return int number of attempts
143
     */
144
    public function getAttemptCount()
145
    {
146
        return $this->execTotal['attempts'] ?: 0;
147
    }
148
149
    /**
150
     * @return int waiting time from push till first execute
151
     */
152
    public function getWaitTime()
153
    {
154
        if ($this->firstExec) {
155
            return $this->firstExec->started_at - $this->pushed_at - $this->delay;
156
        }
157
        return time() - $this->pushed_at - $this->delay;
158
    }
159
160
    /**
161
     * @return string
162
     */
163
    public function getStatus()
164
    {
165
        if ($this->isStopped()) {
166
            return self::STATUS_STOPPED;
167
        }
168
        if (!$this->lastExec) {
169
            return self::STATUS_WAITING;
170
        }
171
        if (!$this->lastExec->isDone() && $this->lastExec->attempt == 1) {
172
            return self::STATUS_STARTED;
173
        }
174
        if ($this->lastExec->isDone() && !$this->lastExec->isFailed()) {
175
            return self::STATUS_DONE;
176
        }
177
        if ($this->lastExec->isDone() && $this->lastExec->retry) {
178
            return self::STATUS_FAILED;
179
        }
180
        if (!$this->lastExec->isDone()) {
181
            return self::STATUS_RESTARTED;
182
        }
183
        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...
184
            return self::STATUS_BURIED;
185
        }
186
        return null;
187
    }
188
189
    /**
190
     * @return Queue|null
191
     */
192
    public function getSender()
193
    {
194
        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...
195
    }
196
197
    /**
198
     * @return bool
199
     */
200
    public function isSenderValid()
201
    {
202
        return $this->getSender() instanceof Queue;
203
    }
204
205
    /**
206
     * @param JobInterface|mixed $job
207
     */
208
    public function setJob($job)
209
    {
210
        $this->job_class = get_class($job);
211
        $data = [];
212
        foreach (get_object_vars($job) as $name => $value) {
213
            $data[$name] = $this->serializeData($value);
214
        }
215
        $this->job_data = Json::encode($data);
216
    }
217
218
    /**
219
     * @return array of job properties
220
     */
221
    public function getJobParams()
222
    {
223
        if (is_resource($this->job_data)) {
224
            $this->job_data = stream_get_contents($this->job_data);
225
        }
226
        $params = [];
227
        foreach (Json::decode($this->job_data) as $name => $value) {
228
            $params[$name] = $this->unserializeData($value);
229
        }
230
        return $params;
231
    }
232
233
    /**
234
     * @return bool
235
     */
236
    public function isJobValid()
237
    {
238
        return is_subclass_of($this->job_class, JobInterface::class);
239
    }
240
241
    /**
242
     * @return JobInterface|mixed
243
     */
244
    public function createJob()
245
    {
246
        return Yii::createObject(['class' => $this->job_class] + $this->getJobParams());
247
    }
248
249
    /**
250
     * @return bool
251
     */
252
    public function canPushAgain()
253
    {
254
        return $this->isSenderValid() && $this->isJobValid();
255
    }
256
257
    /**
258
     * @return bool marked as stopped
259
     */
260
    public function isStopped()
261
    {
262
        return !!$this->stopped_at;
263
    }
264
265
    /**
266
     * @return bool ability to mark as stopped
267
     */
268
    public function canStop()
269
    {
270
        if ($this->isStopped()) {
271
            return false;
272
        }
273
        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...
274
            return false;
275
        }
276
        return true;
277
    }
278
279
    /**
280
     * Marks as stopped
281
     */
282
    public function stop()
283
    {
284
        $this->stopped_at = time();
285
        $this->save(false);
286
    }
287
288
    /**
289
     * @param mixed $data
290
     * @return mixed
291
     */
292
    private function serializeData($data)
293
    {
294
        if (is_object($data)) {
295
            $result = ['=class=' => get_class($data)];
296
            foreach (get_object_vars($data) as $name => $value) {
297
                $result[$name] = $this->serializeData($value);
298
            }
299
            return $result;
300
        }
301
302
        if (is_array($data)) {
303
            $result = [];
304
            foreach ($data as $name => $value) {
305
                $result[$name] = $this->serializeData($value);
306
            }
307
        }
308
309
        return $data;
310
    }
311
312
    /**
313
     * @param mixed $data
314
     * @return mixed
315
     */
316
    private function unserializeData($data)
317
    {
318
        if (!is_array($data)) {
319
            return $data;
320
        }
321
322
        if (!isset($data['=class='])) {
323
            $result = [];
324
            foreach ($data as $key => $value) {
325
                $result[$key] = $this->unserializeData($value);
326
            }
327
            return $result;
328
        }
329
330
        $config = ['class' => $data['=class=']];
331
        unset($data['=class=']);
332
        foreach ($data as $property => $value) {
333
            $config[$property] = $this->unserializeData($value);
334
        }
335
        return Yii::createObject($config);
336
    }
337
}
338