Passed
Push — master ( ad428b...1f3606 )
by Jan-Marten
02:12
created

Transaction::process()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 14
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright MediaCT. All rights reserved.
4
 * https://www.mediact.nl
5
 */
6
7
namespace Johmanx10\Transaction;
8
9
use Johmanx10\Transaction\Exception\FailedRollbackException;
10
use Johmanx10\Transaction\Exception\TransactionRolledBackException;
11
use SplDoublyLinkedList;
12
use Throwable;
13
14
class Transaction implements TransactionInterface
15
{
16
    /** @var OperationInterface[] */
17
    private $operations;
18
19
    /** @var bool */
20
    private $committed = false;
21
22
    /**
23
     * Constructor.
24
     *
25
     * @param OperationInterface ...$operations
26
     */
27 3
    public function __construct(OperationInterface ...$operations)
28
    {
29 3
        $this->operations = $operations;
30 3
    }
31
32
    /**
33
     * Commit the operations in the transaction.
34
     * Roll back operations in reverse order, from the point where a throwable
35
     * was caught.
36
     *
37
     * @return void
38
     *
39
     * @throws TransactionRolledBackException When the transaction was
40
     *   rolled back.
41
     */
42 9
    public function commit(): void
43
    {
44 9
        if ($this->committed === false) {
45 9
            $queue = $this->createQueue(...$this->operations);
46
47
            try {
48 9
                $this->process($queue);
49 6
            } catch (Throwable $exception) {
50 6
                throw new TransactionRolledBackException(
51 6
                    ...$this->rollback($exception, $queue)
52
                );
53
            }
54
        }
55
56 3
        $this->committed = true;
57 3
    }
58
59
    /**
60
     * Whether the current transaction is committed successfully.
61
     *
62
     * @return bool
63
     */
64 3
    public function isCommitted(): bool
65
    {
66 3
        return $this->committed;
67
    }
68
69
    /**
70
     * Rollback the given operations.
71
     *
72
     * @param Throwable                                $exception
73
     * @param SplDoublyLinkedList|OperationInterface[] $queue
74
     *
75
     * @return array|OperationFailureInterface[]
76
     *
77
     * @throws FailedRollbackException When an operation could not be rolled back.
78
     */
79 6
    private function rollback(
80
        Throwable $exception,
81
        SplDoublyLinkedList $queue
82
    ): array {
83
        // Reverse the iterator by telling it last in goes first out.
84 6
        $queue->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
85
86
        // Do not rewind the iterator. It has to continue from where it left off.
87
        // @codingStandardsIgnoreLine
88 6
        for ($failures = []; $queue->valid(); $queue->next()) {
89
            /** @var OperationInterface $operation */
90 6
            $operation = $queue->current();
91
92
            try {
93 6
                $operation->rollback();
94 3
            } catch (Throwable $rollbackException) {
95 3
                throw new FailedRollbackException(
96 3
                    $operation,
97 3
                    0,
98 3
                    $rollbackException,
99
                    // @codeCoverageIgnoreStart
100
                    ...$failures
101
                    // @codeCoverageIgnoreEnd
102
                );
103
            }
104
105 6
            $failures[] = new OperationFailure($operation, $exception);
106 6
            $exception  = null;
107
        }
108
109 3
        return $failures;
110
    }
111
112
    /**
113
     * Process the queued operations.
114
     *
115
     * @param SplDoublyLinkedList|OperationInterface[] $queue
116
     *
117
     * @return void
118
     */
119 3
    private function process(SplDoublyLinkedList $queue): void
120
    {
121
        // Set the iterator to process operations in order.
122
        // First in goes first out and the queue is kept in-tact while traversing.
123 3
        $queue->setIteratorMode(
124
            SplDoublyLinkedList::IT_MODE_FIFO
125 3
            | SplDoublyLinkedList::IT_MODE_KEEP
126
        );
127
128
        // @codingStandardsIgnoreLine
129 3
        for ($queue->rewind(); $queue->valid(); $queue->next()) {
130
            /** @var OperationInterface $operation */
131 2
            $operation = $queue->current();
132 2
            $operation->__invoke();
133
        }
134 3
    }
135
136
    /**
137
     * Create a queue for the given operations.
138
     *
139
     * @param OperationInterface ...$operations
140
     *
141
     * @return SplDoublyLinkedList|OperationInterface[]
142
     */
143 3
    private function createQueue(
144
        OperationInterface ...$operations
145
    ): SplDoublyLinkedList {
146
        /** @var SplDoublyLinkedList|OperationInterface[] $queue */
147 3
        $queue = new SplDoublyLinkedList();
148
149 3
        foreach ($operations as $operation) {
150 2
            $queue->push($operation);
151
        }
152
153 3
        return $queue;
154
    }
155
}
156