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