Passed
Push — master ( 9a453f...7c9700 )
by Roman
01:44
created

PushRecord::getStatusLabel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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