Transaction   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 38
c 0
b 0
f 0
dl 0
loc 150
ccs 41
cts 41
cp 1
rs 10
wmc 13

6 Methods

Rating   Name   Duplication   Size   Complexity  
A isCommitted() 0 3 1
A createQueue() 0 11 2
A rollback() 0 31 3
A commit() 0 15 3
A __construct() 0 3 1
A process() 0 21 3
1
<?php
0 ignored issues
show
Coding Style introduced by
Header blocks must be separated by a single blank line
Loading history...
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 Johmanx10\Transaction\Visitor\AcceptingTransactionInterface;
12
use Johmanx10\Transaction\Visitor\OperationVisitorInterface;
13
use SplDoublyLinkedList;
14
use Throwable;
15
16
class Transaction implements AcceptingTransactionInterface
17
{
18
    /** @var OperationInterface[] */
19
    private $operations;
20
21
    /** @var bool */
22
    private $committed = false;
23
24
    /**
25
     * Constructor.
26
     *
27
     * @param OperationInterface ...$operations
28
     */
29 3
    public function __construct(OperationInterface ...$operations)
30
    {
31 3
        $this->operations = $operations;
32 3
    }
33
34
    /**
35
     * Commit the operations in the transaction.
36
     * Roll back operations in reverse order, from the point where a throwable
37
     * was caught.
38
     *
39
     * @param OperationVisitorInterface ...$visitors
40
     *
41
     * @return void
42
     *
43
     * @throws TransactionRolledBackException When the transaction was
44
     *   rolled back.
45
     */
46 15
    public function commit(OperationVisitorInterface ...$visitors): void
47
    {
48 15
        if ($this->committed === false) {
49 15
            $queue = $this->createQueue(...$this->operations);
50
51
            try {
52 15
                $this->process($queue, ...$visitors);
53 9
            } catch (Throwable $exception) {
54 9
                throw new TransactionRolledBackException(
55 9
                    ...$this->rollback($exception, $queue)
56
                );
57
            }
58
        }
59
60 6
        $this->committed = true;
61 6
    }
62
63
    /**
64
     * Whether the current transaction is committed successfully.
65
     *
66
     * @return bool
67
     */
68 3
    public function isCommitted(): bool
69
    {
70 3
        return $this->committed;
71
    }
72
73
    /**
74
     * Rollback the given operations.
75
     *
76
     * @param Throwable                                $exception
77
     * @param SplDoublyLinkedList|OperationInterface[] $queue
78
     *
79
     * @return array|OperationFailureInterface[]
80
     *
81
     * @throws FailedRollbackException When an operation could not be rolled back.
82
     */
83 6
    private function rollback(
84
        Throwable $exception,
85
        SplDoublyLinkedList $queue
86
    ): array {
87
        // Reverse the iterator by telling it last in goes first out.
88 6
        $queue->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
89
90
        // Do not rewind the iterator. It has to continue from where it left off.
91
        // @codingStandardsIgnoreLine
92 6
        for ($failures = []; $queue->valid(); $queue->next()) {
93
            /** @var OperationInterface $operation */
94 6
            $operation = $queue->current();
95
96
            try {
97 6
                $operation->rollback();
98 3
            } catch (Throwable $rollbackException) {
99 3
                throw new FailedRollbackException(
100 3
                    $operation,
101 3
                    0,
102 3
                    $rollbackException,
103
                    // @codeCoverageIgnoreStart
104
                    ...$failures
105
                    // @codeCoverageIgnoreEnd
106
                );
107
            }
108
109 6
            $failures[] = new OperationFailure($operation, $exception);
110 6
            $exception  = null;
111
        }
112
113 3
        return $failures;
114
    }
115
116
    /**
117
     * Process the queued operations.
118
     *
119
     * @param SplDoublyLinkedList|OperationInterface[] $queue
120
     * @param OperationVisitorInterface                ...$visitors
121
     *
122
     * @return void
123
     */
124 9
    private function process(
125
        SplDoublyLinkedList $queue,
126
        OperationVisitorInterface ...$visitors
127
    ): void {
128
        // Set the iterator to process operations in order.
129
        // First in goes first out and the queue is kept in-tact while traversing.
130 9
        $queue->setIteratorMode(
131
            SplDoublyLinkedList::IT_MODE_FIFO
132 9
            | SplDoublyLinkedList::IT_MODE_KEEP
133
        );
134
135
        // @codingStandardsIgnoreLine
136 9
        for ($queue->rewind(); $queue->valid(); $queue->next()) {
137
            /** @var OperationInterface $operation */
138 7
            $operation = $queue->current();
139
140 7
            foreach ($visitors as $visitor) {
141 5
                $visitor($operation);
142
            }
143
144 7
            $operation->__invoke();
145
        }
146 6
    }
147
148
    /**
149
     * Create a queue for the given operations.
150
     *
151
     * @param OperationInterface ...$operations
152
     *
153
     * @return SplDoublyLinkedList|OperationInterface[]
154
     */
155 3
    private function createQueue(
156
        OperationInterface ...$operations
157
    ): SplDoublyLinkedList {
158
        /** @var SplDoublyLinkedList|OperationInterface[] $queue */
159 3
        $queue = new SplDoublyLinkedList();
160
161 3
        foreach ($operations as $operation) {
162 2
            $queue->push($operation);
163
        }
164
165 3
        return $queue;
166
    }
167
}
168