Passed
Push — int-types ( fff5d9...402d11 )
by Sam
05:52
created

NestedTransactionManager::transactionRollback()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 0
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Connect;
4
5
/**
6
 * TransactionManager decorator that adds virtual nesting support.
7
 * Because this is managed in PHP and not the database, it has the following limitations:
8
 *   - Committing a nested transaction won't change anything until the parent transaction is committed
9
 *   - Rolling back a nested transaction means that the parent transaction must be rolled backed
10
 *
11
 * DBAL describes this behaviour nicely in their docs: https://www.doctrine-project.org/projects/doctrine-dbal/en/2.8/reference/transactions.html#transaction-nesting
12
 */
13
14
class NestedTransactionManager implements TransactionManager
15
{
16
17
    /**
18
     * @var int
19
     */
20
    protected $transactionNesting = 0;
21
22
    /**
23
     * @var TransactionManager
24
     */
25
    protected $child;
26
27
    /**
28
     * Create a NestedTransactionManager
29
     * @param TransactionManager $child The transaction manager that will handle the topmost transaction
30
     */
31
    public function __construct(TransactionManager $child)
32
    {
33
        $this->child = $child;
34
    }
35
36
    /**
37
     * Start a transaction
38
     * @throws DatabaseException on failure
39
     * @return bool True on success
40
     */
41
    public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
42
    {
43
        if ($this->transactionNesting <= 0) {
44
            $this->transactionNesting = 1;
45
            $this->child->transactionStart($transactionMode, $sessionCharacteristics);
46
        } else {
47
            $this->transactionNesting++;
48
        }
49
    }
50
51
    /**
52
     * @inherit
53
     */
54
    public function transactionEnd($chain = false)
55
    {
56
        if ($this->mustRollback) {
57
            throw new DatabaseException("Child transaction was rolled back, so parent can't be committed");
58
        }
59
60
        if ($this->transactionNesting < 1) {
61
            throw new DatabaseException("Not within a transaction, so can't commit");
62
        }
63
64
        $this->transactionNesting--;
65
66
        if ($this->transactionNesting === 0) {
67
            $this->child->transactionEnd();
68
        }
69
70
        if ($chain) {
71
            return $this->transactionStart();
72
        }
73
    }
74
75
    /**
76
     * @inherit
77
     */
78
    public function transactionRollback()
79
    {
80
        if ($this->transactionNesting < 1) {
81
            throw new DatabaseException("Not within a transaction, so can't roll back");
82
        }
83
84
        $this->transactionNesting--;
85
86
        if ($this->transactionNesting === 0) {
87
            $this->child->transactionRollback();
88
            $this->mustRollback = false;
0 ignored issues
show
Bug Best Practice introduced by
The property mustRollback does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
89
        } else {
90
            $this->mustRollback = true;
91
        }
92
    }
93
94
    /**
95
     * Return the depth of the transaction.
96
     *
97
     * @return int
98
     */
99
    public function transactionDepth()
100
    {
101
        return $this->transactionNesting;
102
    }
103
104
}
105