Passed
Push — int-types ( e9891a...c34c73 )
by Sam
06:45
created

NestedTransactionManager::transactionSavepoint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
     * Set to true if all transactions must roll back to the parent
29
     * @var boolean
30
     */
31
    protected $mustRollback = false;
32
33
    /**
34
     * Create a NestedTransactionManager
35
     * @param TransactionManager $child The transaction manager that will handle the topmost transaction
36
     */
37
    public function __construct(TransactionManager $child)
38
    {
39
        $this->child = $child;
40
    }
41
42
    /**
43
     * Start a transaction
44
     * @throws DatabaseException on failure
45
     * @return bool True on success
46
     */
47
    public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
48
    {
49
        if ($this->transactionNesting <= 0) {
50
            $this->transactionNesting = 1;
51
            $this->child->transactionStart($transactionMode, $sessionCharacteristics);
52
53
        } else {
54
            if ($this->child->supportsSavepoints()) {
55
                $this->child->transactionSavepoint("nesting" . $this->transactionNesting);
56
            }
57
            $this->transactionNesting++;
58
        }
59
    }
60
61
    /**
62
     * @inherit
63
     */
64
    public function transactionEnd($chain = false)
65
    {
66
        if ($this->mustRollback) {
67
            throw new DatabaseException("Child transaction was rolled back, so parent can't be committed");
68
        }
69
70
        if ($this->transactionNesting < 1) {
71
            throw new DatabaseException("Not within a transaction, so can't commit");
72
        }
73
74
        $this->transactionNesting--;
75
76
        if ($this->transactionNesting === 0) {
77
            $this->child->transactionEnd();
78
        }
79
80
        if ($chain) {
81
            return $this->transactionStart();
82
        }
83
    }
84
85
    /**
86
     * @inherit
87
     */
88
    public function transactionRollback($savepoint = null)
89
    {
90
        if ($this->transactionNesting < 1) {
91
            throw new DatabaseException("Not within a transaction, so can't roll back");
92
        }
93
94
        if ($savepoint) {
95
            return $this->child->transactionRollback($savepoint);
96
        }
97
98
        $this->transactionNesting--;
99
100
        if ($this->transactionNesting === 0) {
101
            $this->child->transactionRollback();
102
            $this->mustRollback = false;
103
        } else {
104
            if ($this->child->supportsSavepoints()) {
105
                $this->child->transactionRollback("nesting" . $this->transactionNesting);
106
                $this->mustRollback = false;
107
108
            // Without savepoints, parent transactions must roll back if a child one has
109
            } else {
110
                $this->mustRollback = true;
111
            }
112
        }
113
    }
114
115
    /**
116
     * Return the depth of the transaction.
117
     *
118
     * @return int
119
     */
120
    public function transactionDepth()
121
    {
122
        return $this->transactionNesting;
123
    }
124
125
    public function transactionSavepoint($savepoint)
126
    {
127
        return $this->child->transactionSavepoint($savepoint);
128
    }
129
130
    public function supportsSavepoints()
131
    {
132
        return $this->child->supportsSavepoints();
133
    }
134
}
135