Lines 90.90% 20 / 22
Functions and Methods 71.42% 5 / 7
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ManualScheduledTask 90.90% 20 / 22 71.42% 5 / 7 10.08 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 getId 0.00% 0 / 1 0.00% 0 / 1 2
 cancel 100.00% 4 / 4 100.00% 1 / 1 1
 isActive 100.00% 1 / 1 100.00% 1 / 1 1
 getNextRunAt 100.00% 1 / 1 100.00% 1 / 1 1
 armAfter 100.00% 5 / 5 100.00% 1 / 1 2
 runDueOccurrence 88.88% 8 / 9 0.00% 0 / 1 3.01
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Testing\Schedule;
6
7use Closure;
8use Stewart\Contracts\Schedule\ScheduledRun;
9use Stewart\Contracts\Schedule\ScheduledTask;
10use Stewart\Contracts\Time\Instant;
11use Stewart\Contracts\Time\TimerHandle;
12use Stewart\Testing\Time\ManualTimers;
13
14final class ManualScheduledTask implements ScheduledTask
15{
16    private ?Instant $nextRunAt = null;
17
18    private ?TimerHandle $timer = null;
19
20    private bool $active = true;
21
22    /**
23     * @param Closure(Instant): ?Instant $findNextRunAfter
24     * @param Closure(ScheduledRun): void $handler
25     */
26    public function __construct(
27        private readonly string $id,
28        private readonly ManualTimers $timers,
29        private readonly Closure $findNextRunAfter,
30        private readonly Closure $handler,
31    ) {
32        $this->armAfter($timers->clock->getNow());
33    }
34
35    public function getId(): string
36    {
37        return $this->id;
38    }
39
40    public function cancel(): void
41    {
42        $this->active = false;
43        $this->nextRunAt = null;
44        $this->timer?->cancel();
45        $this->timer = null;
46    }
47
48    public function isActive(): bool
49    {
50        return $this->active;
51    }
52
53    public function getNextRunAt(): ?Instant
54    {
55        return $this->nextRunAt;
56    }
57
58    private function armAfter(Instant $moment): void
59    {
60        $this->nextRunAt = ($this->findNextRunAfter)($moment);
61
62        if ($this->nextRunAt === null) {
63            $this->active = false;
64
65            return;
66        }
67
68        $this->timer = $this->timers->startTimer($this->nextRunAt->elapsedSince($this->timers->clock->getNow()), $this->runDueOccurrence(...));
69    }
70
71    private function runDueOccurrence(): void
72    {
73        $scheduledFor = $this->nextRunAt;
74
75        if (!$this->active || $scheduledFor === null) {
76            return;
77        }
78
79        // The next occurrence is fixed before the handler runs, as in the worker.
80        $this->armAfter($scheduledFor);
81        $run = new ScheduledRun($this, $scheduledFor, $this->timers->clock->getNow(), 0);
82        $handler = $this->handler;
83
84        $this->timers->handlerFibers->runSettled(static function () use ($handler, $run): void {
85            $handler($run);
86        });
87    }
88}