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

PushRecord::setPushEnv()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 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 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
 * @property string[] $pushTrace
49
 * @property array $pushEnv
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 Yii::$container->get(Env::class)->db;
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83
    public static function tableName()
84
    {
85
        return Yii::$container->get(Env::class)->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
                'push_id',
136
                'attempts' => 'COUNT(*)',
137
                'errors' => 'COUNT(error)',
138
            ])
139
            ->groupBy('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->reserved_at - $this->pushed_at - $this->push_delay;
158
        }
159
        return time() - $this->pushed_at - $this->push_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 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...
186
            return self::STATUS_BURIED;
187
        }
188
        return null;
189
    }
190
191
    /**
192
     * @return Queue|null
193
     */
194
    public function getSender()
195
    {
196
        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...
197
    }
198
199
    /**
200
     * @return bool
201
     */
202
    public function isSenderValid()
203
    {
204
        return $this->getSender() instanceof Queue;
205
    }
206
207
    /**
208
     * @param JobInterface|mixed $job
209
     */
210
    public function setJob($job)
211
    {
212
        $this->job_class = get_class($job);
213
        $data = [];
214
        foreach (get_object_vars($job) as $name => $value) {
215
            $data[$name] = $this->serializeData($value);
216
        }
217
        $this->job_data = Json::encode($data);
218
    }
219
220
    /**
221
     * @return array of job properties
222
     */
223
    public function getJobParams()
224
    {
225
        if (is_resource($this->job_data)) {
226
            $this->job_data = stream_get_contents($this->job_data);
227
        }
228
        $params = [];
229
        foreach (Json::decode($this->job_data) as $name => $value) {
230
            $params[$name] = $this->unserializeData($value);
231
        }
232
        return $params;
233
    }
234
235
    /**
236
     * @return bool
237
     */
238
    public function isJobValid()
239
    {
240
        return is_subclass_of($this->job_class, JobInterface::class);
241
    }
242
243
    /**
244
     * @return JobInterface|mixed
245
     */
246
    public function createJob()
247
    {
248
        return Yii::createObject(['class' => $this->job_class] + $this->getJobParams());
249
    }
250
251
    /**
252
     * @return string[] trace lines since Queue::push()
253
     */
254
    public function getPushTrace()
255
    {
256
        if ($this->push_trace_data === null) {
257
            return [];
258
        }
259
        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...
260
            $this->push_trace_data = stream_get_contents($this->push_trace_data);
261
        }
262
        $lines = [];
263
        $isFirstFound = false;
264
        foreach (explode("\n", $this->push_trace_data) as $line) {
265
            if (!$isFirstFound && strpos($line, \yii\queue\Queue::class)) {
266
                $isFirstFound = true;
267
            }
268
            if ($isFirstFound) {
269
                list(, $line) = explode(' ', trim($line), 2);
270
                $lines[] = trim($line);
271
            }
272
        }
273
        return $lines;
274
    }
275
276
    /**
277
     * @return array
278
     */
279
    public function getPushEnv()
280
    {
281
        if ($this->push_env_data === null) {
282
            return [];
283
        }
284
        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...
285
            $this->push_env_data = stream_get_contents($this->push_env_data);
286
        }
287
        return unserialize($this->push_env_data);
288
    }
289
290
    /**
291
     * @return bool
292
     */
293
    public function canPushAgain()
294
    {
295
        return $this->isSenderValid() && $this->isJobValid();
296
    }
297
298
    /**
299
     * @return bool marked as stopped
300
     */
301
    public function isStopped()
302
    {
303
        return !!$this->stopped_at;
304
    }
305
306
    /**
307
     * @return bool ability to mark as stopped
308
     */
309
    public function canStop()
310
    {
311
        if ($this->isStopped()) {
312
            return false;
313
        }
314
        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...
315
            return false;
316
        }
317
        return true;
318
    }
319
320
    /**
321
     * Marks as stopped
322
     */
323
    public function stop()
324
    {
325
        $this->stopped_at = time();
326
        $this->save(false);
327
    }
328
329
    /**
330
     * @param mixed $data
331
     * @return mixed
332
     */
333
    private function serializeData($data)
334
    {
335
        if (is_object($data)) {
336
            $result = ['=class=' => get_class($data)];
337
            foreach (get_object_vars($data) as $name => $value) {
338
                $result[$name] = $this->serializeData($value);
339
            }
340
            return $result;
341
        }
342
343
        if (is_array($data)) {
344
            $result = [];
345
            foreach ($data as $name => $value) {
346
                $result[$name] = $this->serializeData($value);
347
            }
348
        }
349
350
        return $data;
351
    }
352
353
    /**
354
     * @param mixed $data
355
     * @return mixed
356
     */
357
    private function unserializeData($data)
358
    {
359
        if (!is_array($data)) {
360
            return $data;
361
        }
362
363
        if (!isset($data['=class='])) {
364
            $result = [];
365
            foreach ($data as $key => $value) {
366
                $result[$key] = $this->unserializeData($value);
367
            }
368
            return $result;
369
        }
370
371
        $config = ['class' => $data['=class=']];
372
        unset($data['=class=']);
373
        foreach ($data as $property => $value) {
374
            $config[$property] = $this->unserializeData($value);
375
        }
376
        return Yii::createObject($config);
377
    }
378
}
379