Passed
Pull Request — master (#8)
by Insolita
01:41
created

PushRecord::isStopped()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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