Completed
Push — master ( 5221b3...bbb882 )
by Steve
02:07
created

Transaction::executeStep()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace MeadSteve\Tale;
4
5
use MeadSteve\Tale\Exceptions\FailedApplyingAllCompensations;
6
use MeadSteve\Tale\Execution\CompletedStep;
7
use MeadSteve\Tale\Execution\Failure;
8
use MeadSteve\Tale\Execution\Success;
9
use MeadSteve\Tale\Execution\TransactionResult;
10
use MeadSteve\Tale\State\CloneableState;
11
use MeadSteve\Tale\Steps\FinalisingStep;
12
use MeadSteve\Tale\Steps\NamedStep;
13
use MeadSteve\Tale\Steps\Step;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\NullLogger;
16
17
class Transaction
18
{
19
    /**
20
     * @var Step[]
21
     */
22
    private $steps = [];
23
24
    /**
25
     * @var LoggerInterface
26
     */
27
    private $logger;
28
29
    public function __construct(LoggerInterface $logger = null)
30
    {
31
        $this->logger = $logger ?? new NullLogger();
32
    }
33
34
35
    /**
36
     * Adds a step to the transaction. With the following args:
37
     *
38
     *    (Step)                  -> Adds the step to transaction
39
     *    (Closure, Closure)      -> Creates a step with the first lambda
40
     *                               as the execute and the second as compensate
41
     *    (Closure, Closure, str) -> Same as above but named
42
     *
43
     * @param mixed ...$args
44
     * @return Transaction
45
     */
46
    public function add(...$args): Transaction
47
    {
48
        $step = StepBuilder::build(...$args);
49
        return $this->addStep($step);
50
    }
51
52
    /**
53
     * Runs each step in the transaction
54
     *
55
     * @param mixed $startingState the state to pass in to the first step
56
     * @return TransactionResult
57
     */
58
    public function run($startingState = []): TransactionResult
59
    {
60
        $this->logger->debug("Running transaction");
61
        $state = $startingState;
62
        $completedSteps = [];
63
        foreach ($this->steps as $key => $step) {
64
            try {
65
                $state = $this->executeStep($key, $step, $state);
66
                $completedSteps[] = new CompletedStep($step, $state, $key);
67
            } catch (\Throwable $failure) {
68
                $this->logger->debug("Failed executing {$this->stepName($step)} step [$key]");
69
                $this->revertCompletedSteps($completedSteps);
70
                $this->logger->debug("Finished compensating all previous steps");
71
                return new Failure($failure);
72
            }
73
        }
74
        $this->finaliseSteps($completedSteps);
75
        return new Success($state);
76
    }
77
78
    private function addStep(Step $step): Transaction
79
    {
80
        $this->logger->debug("Adding {$this->stepName($step)} to transaction definition");
81
        $this->steps[] = $step;
82
        return $this;
83
    }
84
85
    /**
86
     * @param CompletedStep[] $completedSteps
87
     */
88
    private function revertCompletedSteps(array $completedSteps): void
89
    {
90
        $errors = [];
91
        foreach (array_reverse($completedSteps) as $completedStep) {
92
            $step = $completedStep->step;
93
            $stepId = $completedStep->stepId;
94
            try {
95
                $this->logger->debug("Compensating for step {$this->stepName($step)} [{$stepId}]");
96
                $step->compensate($completedStep->state);
97
                $this->logger->debug("Compensation complete for step {$this->stepName($step)} [{$stepId}]");
98
            } catch (\Throwable $error) {
99
                $this->logger->debug("Compensation failed for step {$this->stepName($step)} [{$stepId}]");
100
                $errors[] = $error;
101
            }
102
        }
103
        if (sizeof($errors) !== 0) {
104
            throw new FailedApplyingAllCompensations($errors);
105
        }
106
    }
107
108
    /**
109
     * @param CompletedStep[] $completedSteps
110
     */
111
    private function finaliseSteps($completedSteps): void
112
    {
113
        foreach ($completedSteps as $completedStep) {
114
            $step = $completedStep->step;
115
            if ($step instanceof FinalisingStep) {
116
                $stepId = $completedStep->stepId;
117
                $this->logger->debug("Finalising step {$this->stepName($step)} [{$stepId}]");
118
                $step->finalise($completedStep->state);
119
                $this->logger->debug("Finalising step {$this->stepName($step)} [{$stepId}]");
120
            }
121
        }
122
    }
123
124
    private function stepName(Step $step): string
125
    {
126
        if ($step instanceof NamedStep) {
127
            return "`{$step->stepName()}`";
128
        }
129
        return "anonymous step";
130
    }
131
132
    private function executeStep($number, Step $step, $state)
133
    {
134
        if ($state instanceof CloneableState) {
135
            $state = $state->cloneState();
136
        }
137
        $this->logger->debug("Executing {$this->stepName($step)} step [$number]");
138
        $state = $step->execute($state);
139
        $this->logger->debug("Execution of {$this->stepName($step)} step [$number] complete");
140
        return $state;
141
    }
142
}
143