Job   F
last analyzed

Complexity

Total Complexity 99

Size/Duplication

Total Lines 779
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 172
dl 0
loc 779
rs 2
c 0
b 0
f 0
wmc 99

69 Methods

Rating   Name   Duplication   Size   Complexity  
A isInFinalState() 0 3 3
A isNonSuccessfulFinalState() 0 8 1
A getRetryJobs() 0 3 1
A isFinished() 0 3 1
A isTerminated() 0 3 1
A setExitCode() 0 3 1
A setArgs() 0 3 1
A getExitCode() 0 3 1
A setMaxRuntime() 0 3 1
A setRuntime() 0 3 1
A isCanceled() 0 3 1
A getWorkerName() 0 3 1
A getCommand() 0 3 1
A setCommand() 0 3 1
A getMaxRetries() 0 3 1
A __toString() 0 6 1
A setCreatedAt() 0 3 1
A getId() 0 3 1
A getQueue() 0 3 1
A setQueue() 0 3 1
A getErrorOutput() 0 3 1
A getPriority() 0 3 1
A hasRetryJobs() 0 3 1
A setSchedule() 0 6 2
A setStartedAt() 0 3 1
A hasRetryJob() 0 3 1
A __construct() 0 6 1
A mightHaveStarted() 0 15 5
A isNew() 0 3 1
A addDependency() 0 11 3
A getMaxRuntime() 0 3 1
C setState() 0 36 12
A getClosedAt() 0 3 1
A getArgs() 0 3 1
A addOutput() 0 3 1
A getCreatedAt() 0 3 1
A isPending() 0 3 1
A getExecuteAfter() 0 3 1
A isFailed() 0 3 1
A isRetryAllowed() 0 9 2
A __clone() 0 11 1
A setClosedAt() 0 3 1
A setExecuteAfter() 0 3 1
A setPriority() 0 3 1
A getStartedAt() 0 3 1
A isStartable() 0 9 3
A addRetryJob() 0 8 3
A getRuntime() 0 3 1
A getSchedule() 0 3 1
A isRunning() 0 3 1
A hasDependency() 0 3 1
A isClosedNonSuccessful() 0 3 1
A getDependencies() 0 3 1
A isRetryJob() 0 3 1
A setMaxRetries() 0 3 1
A getCheckedAt() 0 3 1
A setOutput() 0 3 1
A setOriginalJob() 0 6 2
A getStates() 0 11 1
A isRetried() 0 9 3
A setCheckedAt() 0 3 1
A getOriginalJob() 0 7 2
A setWorkerName() 0 3 1
A getOutput() 0 3 1
A setErrorOutput() 0 3 1
A removeRetryJob() 0 5 2
A getState() 0 3 1
A isIncomplete() 0 3 1
A addErrorOutput() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Job 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 Job, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Setono\SyliusSchedulerPlugin\Model;
6
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\Common\Collections\Collection;
9
use Setono\SyliusSchedulerPlugin\Exception\LogicException;
10
11
class Job implements JobInterface
12
{
13
    /**
14
     * @var int
15
     */
16
    private $id;
17
18
    /**
19
     * @var ScheduleInterface|null
20
     */
21
    private $schedule;
22
23
    /**
24
     * @var string|null
25
     */
26
    private $command;
27
28
    /**
29
     * @var array
30
     */
31
    private $args = [];
32
33
    /**
34
     * @var string
35
     */
36
    private $state = self::STATE_NEW;
37
38
    /**
39
     * @var string
40
     */
41
    private $queue = self::DEFAULT_QUEUE;
42
43
    /**
44
     * @var int
45
     */
46
    private $priority = self::PRIORITY_DEFAULT;
47
48
    /**
49
     * @var \DateTime
50
     */
51
    private $createdAt;
52
53
    /**
54
     * @var ?\DateTime
55
     */
56
    private $startedAt;
57
58
    /**
59
     * @var ?\DateTime
60
     */
61
    private $checkedAt;
62
63
    /**
64
     * @var ?string
65
     */
66
    private $workerName;
67
68
    /**
69
     * @var ?\DateTime
70
     */
71
    private $executeAfter;
72
73
    /**
74
     * @var ArrayCollection|JobInterface[]
75
     */
76
    private $dependencies;
77
78
    /**
79
     * @var ?\DateTime
80
     */
81
    private $closedAt;
82
83
    /**
84
     * @var ?string
85
     */
86
    private $output;
87
88
    /**
89
     * @var ?string
90
     */
91
    private $errorOutput;
92
93
    /**
94
     * @var ?int
95
     */
96
    private $exitCode;
97
98
    /**
99
     * @var int
100
     */
101
    private $maxRuntime = 0;
102
103
    /**
104
     * @var ?int
105
     */
106
    private $runtime;
107
108
    /**
109
     * @var int
110
     */
111
    private $maxRetries = 0;
112
113
    /**
114
     * @var ?JobInterface
115
     */
116
    private $originalJob;
117
118
    /**
119
     * @var ArrayCollection|JobInterface[]
120
     */
121
    private $retryJobs;
122
123
    /**
124
     * @param string $state
125
     *
126
     * @return bool
127
     */
128
    public static function isNonSuccessfulFinalState(string $state): bool
129
    {
130
        return \in_array($state, [
131
            self::STATE_CANCELED,
132
            self::STATE_FAILED,
133
            self::STATE_INCOMPLETE,
134
            self::STATE_TERMINATED,
135
        ], true);
136
    }
137
138
    /**
139
     * @return array|string[]
140
     */
141
    public static function getStates(): array
142
    {
143
        return [
144
            self::STATE_NEW,
145
            self::STATE_PENDING,
146
            self::STATE_CANCELED,
147
            self::STATE_RUNNING,
148
            self::STATE_FINISHED,
149
            self::STATE_FAILED,
150
            self::STATE_TERMINATED,
151
            self::STATE_INCOMPLETE,
152
        ];
153
    }
154
155
    public function __construct()
156
    {
157
        $this->createdAt = new \DateTime();
158
        $this->executeAfter = new \DateTime('-1 second');
159
        $this->dependencies = new ArrayCollection();
160
        $this->retryJobs = new ArrayCollection();
161
    }
162
163
    public function __clone()
164
    {
165
        $this->state = self::STATE_PENDING;
166
        $this->createdAt = new \DateTime();
167
        $this->startedAt = null;
168
        $this->checkedAt = null;
169
        $this->closedAt = null;
170
        $this->workerName = null;
171
        $this->output = null;
172
        $this->errorOutput = null;
173
        $this->exitCode = null;
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function getId()
180
    {
181
        return $this->id;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function getSchedule(): ?ScheduleInterface
188
    {
189
        return $this->schedule;
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195
    public function setSchedule(?ScheduleInterface $schedule): void
196
    {
197
        $this->schedule = $schedule;
198
199
        if ($schedule instanceof ScheduleInterface) {
200
            $schedule->addJob($this);
201
        }
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function getState(): string
208
    {
209
        return $this->state;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function setWorkerName(?string $workerName): void
216
    {
217
        $this->workerName = $workerName;
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function getWorkerName(): ?string
224
    {
225
        return $this->workerName;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231
    public function setPriority(int $priority): void
232
    {
233
        $this->priority = $priority * -1;
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function getPriority(): int
240
    {
241
        return $this->priority * -1;
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247
    public function isInFinalState(): bool
248
    {
249
        return !$this->isNew() && !$this->isPending() && !$this->isRunning();
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function isStartable(): bool
256
    {
257
        foreach ($this->dependencies as $dependency) {
258
            if ($dependency->getState() !== self::STATE_FINISHED) {
259
                return false;
260
            }
261
        }
262
263
        return true;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function setState(string $state): void
270
    {
271
        if ($state === $this->state) {
272
            return;
273
        }
274
275
        switch ($this->state) {
276
            case self::STATE_NEW:
277
                if (self::STATE_CANCELED === $state) {
278
                    $this->closedAt = new \DateTime();
279
                }
280
281
                break;
282
            case self::STATE_PENDING:
283
                if ($state === self::STATE_RUNNING) {
284
                    $this->startedAt = new \DateTime();
285
                    $this->checkedAt = new \DateTime();
286
                } elseif ($state === self::STATE_CANCELED) {
287
                    $this->closedAt = new \DateTime();
288
                }
289
290
                break;
291
            case self::STATE_RUNNING:
292
                $this->closedAt = new \DateTime();
293
294
                break;
295
            case self::STATE_FINISHED:
296
            case self::STATE_FAILED:
297
            case self::STATE_TERMINATED:
298
            case self::STATE_INCOMPLETE:
299
                break;
300
            default:
301
                throw new LogicException('The previous cases were exhaustive. Unknown state: ' . $this->state);
302
        }
303
304
        $this->state = $state;
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     */
310
    public function setCreatedAt(\DateTime $createdAt): void
311
    {
312
        $this->createdAt = $createdAt;
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318
    public function getCreatedAt(): \DateTime
319
    {
320
        return $this->createdAt;
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326
    public function setClosedAt(?\DateTime $closedAt): void
327
    {
328
        $this->closedAt = $closedAt;
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334
    public function getClosedAt(): ?\DateTime
335
    {
336
        return $this->closedAt;
337
    }
338
339
    /**
340
     * {@inheritdoc}
341
     */
342
    public function getExecuteAfter(): ?\DateTime
343
    {
344
        return $this->executeAfter;
345
    }
346
347
    /**
348
     * {@inheritdoc}
349
     */
350
    public function setExecuteAfter(?\DateTime $executeAfter): void
351
    {
352
        $this->executeAfter = $executeAfter;
353
    }
354
355
    /**
356
     * {@inheritdoc}
357
     */
358
    public function getCommand(): ?string
359
    {
360
        return $this->command;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366
    public function setCommand(string $command): void
367
    {
368
        $this->command = $command;
369
    }
370
371
    /**
372
     * {@inheritdoc}
373
     */
374
    public function getArgs(): array
375
    {
376
        return $this->args;
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382
    public function setArgs(array $args): void
383
    {
384
        $this->args = $args;
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390
    public function isClosedNonSuccessful(): bool
391
    {
392
        return self::isNonSuccessfulFinalState($this->state);
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398
    public function getDependencies(): Collection
399
    {
400
        return $this->dependencies;
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     */
406
    public function hasDependency(JobInterface $job): bool
407
    {
408
        return $this->dependencies->contains($job);
409
    }
410
411
    /**
412
     * {@inheritdoc}
413
     */
414
    public function addDependency(JobInterface $job): void
415
    {
416
        if ($this->dependencies->contains($job)) {
417
            return;
418
        }
419
420
        if ($this->mightHaveStarted()) {
421
            throw new \LogicException('You cannot add dependencies to a job which might have been started already.');
422
        }
423
424
        $this->dependencies->add($job);
425
    }
426
427
    /**
428
     * {@inheritdoc}
429
     */
430
    public function addOutput(string $output): void
431
    {
432
        $this->output .= $output;
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     */
438
    public function addErrorOutput(string $output): void
439
    {
440
        $this->errorOutput .= $output;
441
    }
442
443
    /**
444
     * {@inheritdoc}
445
     */
446
    public function setOutput(?string $output): void
447
    {
448
        $this->output = $output;
449
    }
450
451
    /**
452
     * {@inheritdoc}
453
     */
454
    public function setErrorOutput(?string $output): void
455
    {
456
        $this->errorOutput = $output;
457
    }
458
459
    /**
460
     * {@inheritdoc}
461
     */
462
    public function getOutput(): ?string
463
    {
464
        return $this->output;
465
    }
466
467
    /**
468
     * {@inheritdoc}
469
     */
470
    public function getErrorOutput(): ?string
471
    {
472
        return $this->errorOutput;
473
    }
474
475
    /**
476
     * {@inheritdoc}
477
     */
478
    public function setExitCode(?int $code): void
479
    {
480
        $this->exitCode = $code;
481
    }
482
483
    /**
484
     * {@inheritdoc}
485
     */
486
    public function getExitCode(): ?int
487
    {
488
        return $this->exitCode;
489
    }
490
491
    /**
492
     * {@inheritdoc}
493
     */
494
    public function setMaxRuntime(int $maxRuntime): void
495
    {
496
        $this->maxRuntime = $maxRuntime;
497
    }
498
499
    /**
500
     * {@inheritdoc}
501
     */
502
    public function getMaxRuntime(): int
503
    {
504
        return $this->maxRuntime;
505
    }
506
507
    /**
508
     * {@inheritdoc}
509
     */
510
    public function getRuntime(): ?int
511
    {
512
        return $this->runtime;
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518
    public function setRuntime(?int $runtime): void
519
    {
520
        $this->runtime = $runtime;
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526
    public function setStartedAt(?\DateTime $startedAt): void
527
    {
528
        $this->startedAt = $startedAt;
529
    }
530
531
    /**
532
     * {@inheritdoc}
533
     */
534
    public function getStartedAt(): ?\DateTime
535
    {
536
        return $this->startedAt;
537
    }
538
539
    /**
540
     * {@inheritdoc}
541
     */
542
    public function getMaxRetries(): int
543
    {
544
        return $this->maxRetries;
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550
    public function setMaxRetries(int $maxRetries): void
551
    {
552
        $this->maxRetries = $maxRetries;
553
    }
554
555
    /**
556
     * {@inheritdoc}
557
     */
558
    public function isRetryAllowed(): bool
559
    {
560
        // If no retries are allowed, we can bail out directly, and we
561
        // do not need to initialize the retryJobs relation.
562
        if (0 === $this->maxRetries) {
563
            return false;
564
        }
565
566
        return count($this->retryJobs) < $this->maxRetries;
567
    }
568
569
    /**
570
     * {@inheritdoc}
571
     */
572
    public function getOriginalJob(): JobInterface
573
    {
574
        if (null === $this->originalJob) {
575
            return $this;
576
        }
577
578
        return $this->originalJob;
579
    }
580
581
    /**
582
     * {@inheritdoc}
583
     */
584
    public function setOriginalJob(?JobInterface $job): void
585
    {
586
        $this->originalJob = $job;
587
588
        if ($job instanceof JobInterface) {
589
            $job->addRetryJob($this);
590
        }
591
    }
592
593
    /**
594
     * {@inheritdoc}
595
     */
596
    public function addRetryJob(JobInterface $job): void
597
    {
598
        if (!$this->hasRetryJob($job)) {
599
            $this->retryJobs->add($job);
600
        }
601
602
        if ($this !== $job->getOriginalJob()) {
0 ignored issues
show
introduced by
The condition $this !== $job->getOriginalJob() is always true.
Loading history...
603
            $job->setOriginalJob($this);
604
        }
605
    }
606
607
    /**
608
     * {@inheritdoc}
609
     */
610
    public function getRetryJobs(): Collection
611
    {
612
        return $this->retryJobs;
613
    }
614
615
    /**
616
     * {@inheritdoc}
617
     */
618
    public function hasRetryJob(JobInterface $job): bool
619
    {
620
        return $this->retryJobs->contains($job);
621
    }
622
623
    /**
624
     * {@inheritdoc}
625
     */
626
    public function hasRetryJobs(): bool
627
    {
628
        return !$this->retryJobs->isEmpty();
629
    }
630
631
    /**
632
     * {@inheritdoc}
633
     */
634
    public function removeRetryJob(JobInterface $job): void
635
    {
636
        if ($this->hasRetryJob($job)) {
637
            $job->setOriginalJob(null);
638
            $this->retryJobs->removeElement($job);
639
        }
640
    }
641
642
    /**
643
     * {@inheritdoc}
644
     */
645
    public function isRetryJob(): bool
646
    {
647
        return null !== $this->originalJob;
648
    }
649
650
    /**
651
     * {@inheritdoc}
652
     */
653
    public function isRetried(): bool
654
    {
655
        foreach ($this->retryJobs as $job) {
656
            if (!$job->isInFinalState()) {
657
                return true;
658
            }
659
        }
660
661
        return false;
662
    }
663
664
    /**
665
     * {@inheritdoc}
666
     */
667
    public function setCheckedAt(?\DateTime $checkedAt): void
668
    {
669
        $this->checkedAt = $checkedAt;
670
    }
671
672
    /**
673
     * {@inheritdoc}
674
     */
675
    public function getCheckedAt(): ?\DateTime
676
    {
677
        return $this->checkedAt;
678
    }
679
680
    /**
681
     * {@inheritdoc}
682
     */
683
    public function setQueue(string $queue): void
684
    {
685
        $this->queue = $queue;
686
    }
687
688
    /**
689
     * {@inheritdoc}
690
     */
691
    public function getQueue(): string
692
    {
693
        return $this->queue;
694
    }
695
696
    /**
697
     * {@inheritdoc}
698
     */
699
    public function isNew(): bool
700
    {
701
        return self::STATE_NEW === $this->state;
702
    }
703
704
    /**
705
     * {@inheritdoc}
706
     */
707
    public function isPending(): bool
708
    {
709
        return self::STATE_PENDING === $this->state;
710
    }
711
712
    /**
713
     * {@inheritdoc}
714
     */
715
    public function isCanceled(): bool
716
    {
717
        return self::STATE_CANCELED === $this->state;
718
    }
719
720
    /**
721
     * {@inheritdoc}
722
     */
723
    public function isRunning(): bool
724
    {
725
        return self::STATE_RUNNING === $this->state;
726
    }
727
728
    /**
729
     * {@inheritdoc}
730
     */
731
    public function isTerminated(): bool
732
    {
733
        return self::STATE_TERMINATED === $this->state;
734
    }
735
736
    /**
737
     * {@inheritdoc}
738
     */
739
    public function isFailed(): bool
740
    {
741
        return self::STATE_FAILED === $this->state;
742
    }
743
744
    /**
745
     * {@inheritdoc}
746
     */
747
    public function isFinished(): bool
748
    {
749
        return self::STATE_FINISHED === $this->state;
750
    }
751
752
    /**
753
     * {@inheritdoc}
754
     */
755
    public function isIncomplete(): bool
756
    {
757
        return self::STATE_INCOMPLETE === $this->state;
758
    }
759
760
    /**
761
     * {@inheritdoc}
762
     */
763
    public function __toString()
764
    {
765
        return sprintf(
766
            'Job(id = %s, command = "%s")',
767
            $this->id,
768
            $this->command
769
        );
770
    }
771
772
    /**
773
     * @return bool
774
     */
775
    private function mightHaveStarted(): bool
776
    {
777
        if (null === $this->id) {
778
            return false;
779
        }
780
781
        if (self::STATE_NEW === $this->state) {
782
            return false;
783
        }
784
785
        if (self::STATE_PENDING === $this->state && !$this->isStartable()) {
786
            return false;
787
        }
788
789
        return true;
790
    }
791
}
792