PushRecord   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 54
eloc 99
c 5
b 0
f 0
dl 0
loc 301
rs 6.4799

25 Methods

Rating   Name   Duplication   Size   Complexity  
A serializeData() 0 18 5
A createJob() 0 3 1
A getAttemptCount() 0 3 1
A getLastExec() 0 3 1
A isStopped() 0 3 1
A getChildren() 0 3 1
A canStop() 0 9 5
A getJobParams() 0 10 3
A getSender() 0 3 1
A getStatusLabel() 0 15 2
A getFirstExec() 0 3 1
A getParent() 0 3 1
A canPushAgain() 0 3 2
A tableName() 0 3 1
A getExecs() 0 3 1
A getWaitTime() 0 6 2
A unserializeData() 0 20 5
A find() 0 3 1
A isSenderValid() 0 3 1
A stop() 0 4 1
C getStatus() 0 24 12
A getDb() 0 3 1
A setJob() 0 8 2
A getExecTotal() 0 10 1
A isJobValid() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like PushRecord often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PushRecord, and based on these observations, apply Extract Interface, too.

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