Passed
Pull Request — master (#5)
by Steve
02:03
created

Transaction::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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