Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
SequentialBackgroundProcess
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 14
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getPayload
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getErrors
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVersion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStep
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRetries
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStartTime
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCompletionTime
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getResult
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 provideAllowedMethods
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 cancel
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 runStep
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2namespace Apie\Core\BackgroundProcess;
3
4use Apie\Core\ApieLib;
5use Apie\Core\Attributes\AlwaysDisabled;
6use Apie\Core\Attributes\Context;
7use Apie\Core\Attributes\FakeCount;
8use Apie\Core\Attributes\StaticCheck;
9use Apie\Core\Context\ApieContext;
10use Apie\Core\ContextConstants;
11use Apie\Core\Dto\MessageAndTimestamp;
12use Apie\Core\Entities\EntityWithStatesInterface;
13use Apie\Core\Identifiers\PascalCaseSlug;
14use Apie\Core\Identifiers\Ulid;
15use Apie\Core\Lists\ItemHashmap;
16use Apie\Core\Lists\ItemList;
17use Apie\Core\Lists\MessageAndTimestampList;
18use Apie\Core\Lists\StringList;
19use Apie\Core\ValueObjects\DatabaseText;
20use DateTimeInterface;
21use ReflectionClass;
22use Throwable;
23
24#[FakeCount(0)]
25class SequentialBackgroundProcess implements EntityWithStatesInterface
26{
27    private int $version;
28    private int $step;
29    private int $retries = 0;
30    private DateTimeInterface $startTime;
31    private ?DateTimeInterface $completionTime = null;
32    private DatabaseText $className;
33    private BackgroundProcessStatus $status = BackgroundProcessStatus::Active;
34    private SequentialBackgroundProcessIdentifier $id;
35    private mixed $result = null;
36    private MessageAndTimestampList $errors;
37
38    #[StaticCheck(new AlwaysDisabled())]
39    public function __construct(
40        BackgroundProcessDeclaration $backgroundProcessDeclaration,
41        private ItemHashmap|ItemList $payload
42    ) {
43        $this->className = new DatabaseText(get_debug_type($backgroundProcessDeclaration));
44        $this->version = $backgroundProcessDeclaration->getCurrentVersion();
45        $this->step = 0;
46        $this->startTime = ApieLib::getPsrClock()->now();
47        $this->id = new SequentialBackgroundProcessIdentifier(
48            new PascalCaseSlug((new ReflectionClass($backgroundProcessDeclaration))->getShortName()),
49            Ulid::createRandom()
50        );
51        $this->errors = new MessageAndTimestampList();
52    }
53
54    public function getPayload(): ItemHashmap|ItemList
55    {
56        return $this->payload;
57    }
58
59    public function getErrors(): MessageAndTimestampList
60    {
61        return $this->errors;
62    }
63
64    public function getId(): SequentialBackgroundProcessIdentifier
65    {
66        return $this->id;
67    }
68
69    public function getVersion(): int
70    {
71        return $this->version;
72    }
73
74    public function getStep(): int
75    {
76        return $this->step;
77    }
78
79    public function getRetries(): int
80    {
81        return $this->retries;
82    }
83
84    public function getStartTime(): DateTimeInterface
85    {
86        return $this->startTime;
87    }
88
89    public function getCompletionTime(): ?DateTimeInterface
90    {
91        return $this->completionTime;
92    }
93
94    public function getStatus(): BackgroundProcessStatus
95    {
96        return $this->status;
97    }
98
99    public function getResult(): mixed
100    {
101        return $this->result;
102    }
103
104    public function provideAllowedMethods(): StringList
105    {
106        if ($this->status === BackgroundProcessStatus::Active) {
107            return new StringList(['cancel', 'runStep']);
108        }
109
110        return new StringList([]);
111    }
112
113    public function cancel(): void
114    {
115        if ($this->status !== BackgroundProcessStatus::Active) {
116            throw new \LogicException('Process ' . $this->id . ' can not be executed!');
117        }
118        $this->status = BackgroundProcessStatus::Canceled;
119    }
120
121    public function runStep(#[Context()] ApieContext $apieContext): void
122    {
123        if ($this->status !== BackgroundProcessStatus::Active) {
124            throw new \LogicException('Process ' . $this->id . ' can not be executed!');
125        }
126        $apieContext = $apieContext->withContext(ContextConstants::BACKGROUND_PROCESS, $this->result);
127        $maxRetries = 1;
128        try {
129            $className = $this->className->toNative();
130            $maxRetries = $className::getMaxRetries($this->version);
131            $steps = array_values($className::retrieveDeclaration($this->version));
132            if (isset($steps[$this->step])) {
133                $this->result = call_user_func($steps[$this->step], $apieContext, $this->payload);
134                $this->step++;
135                $this->retries = 0;
136            } else {
137                $this->completionTime = ApieLib::getPsrClock()->now();
138                $this->status = BackgroundProcessStatus::Finished;
139            }
140        } catch (Throwable $error) {
141            $this->errors[] = new MessageAndTimestamp(
142                $error->getMessage(),
143                ApieLib::getPsrClock()->now()
144            );
145            $this->retries++;
146            if ($this->retries >= $maxRetries) {
147                $this->status = BackgroundProcessStatus::TooManyErrors;
148            }
149        }
150    }
151}