Passed
Push — phpstan ( c4fbd2...07dbe1 )
by Sam
08:10
created

NestedTransactionManager::__construct()   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
        } else {
53
            if ($this->child->supportsSavepoints()) {
54
                $this->child->transactionSavepoint("nesting" . $this->transactionNesting);
55
            }
56
            $this->transactionNesting++;
57
        }
58
    }
59
60
    public function transactionEnd($chain = false)
61
    {
62
        if ($this->mustRollback) {
63
            throw new DatabaseException("Child transaction was rolled back, so parent can't be committed");
64
        }
65
66
        if ($this->transactionNesting < 1) {
67
            throw new DatabaseException("Not within a transaction, so can't commit");
68
        }
69
70
        $this->transactionNesting--;
71
72
        if ($this->transactionNesting === 0) {
73
            $this->child->transactionEnd();
74
        }
75
76
        if ($chain) {
77
            return $this->transactionStart();
78
        }
79
    }
80
81
    public function transactionRollback($savepoint = null)
82
    {
83
        if ($this->transactionNesting < 1) {
84
            throw new DatabaseException("Not within a transaction, so can't roll back");
85
        }
86
87
        if ($savepoint) {
88
            return $this->child->transactionRollback($savepoint);
89
        }
90
91
        $this->transactionNesting--;
92
93
        if ($this->transactionNesting === 0) {
94
            $this->child->transactionRollback();
95
            $this->mustRollback = false;
96
        } else {
97
            if ($this->child->supportsSavepoints()) {
98
                $this->child->transactionRollback("nesting" . $this->transactionNesting);
99
                $this->mustRollback = false;
100
101
            // Without savepoints, parent transactions must roll back if a child one has
102
            } else {
103
                $this->mustRollback = true;
104
            }
105
        }
106
    }
107
108
    /**
109
     * Return the depth of the transaction.
110
     *
111
     * @return int
112
     */
113
    public function transactionDepth()
114
    {
115
        return $this->transactionNesting;
116
    }
117
118
    public function transactionSavepoint($savepoint)
119
    {
120
        return $this->child->transactionSavepoint($savepoint);
121
    }
122
123
    public function supportsSavepoints()
124
    {
125
        return $this->child->supportsSavepoints();
126
    }
127
128
    /**
129
     * In error condition, set transactionNesting to zero
130
     * @return void
131
     */
132
    public function resetTransactionNesting()
133
    {
134
        $this->transactionNesting = 0;
135
    }
136
}
137