Completed
Push — master ( 528fe3...530777 )
by Ondřej
03:14
created

handleTransactionRollback()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 8
Ratio 36.36 %

Importance

Changes 0
Metric Value
dl 8
loc 22
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 0
1
<?php
2
namespace Ivory\Connection;
3
4
use Ivory\Exception\InternalException;
5
6
class ConnConfigTransactionWatcher implements ITransactionControlObserver
7
{
8
    const SCOPE_TRANSACTION = 'trans';
9
    const SCOPE_SESSION = 'session';
10
11
    const RESET_ALL = '';
12
13
    private static $emptyStruct = [
14
        'name' => null,
15
        self::SCOPE_TRANSACTION => [],
16
        self::SCOPE_SESSION => [],
17
    ];
18
19
    private $connConfig;
20
21
    /** @var bool whether currently in a transaction */
22
    private $inTrans = false;
23
24
    /**
25
     * @var array[]|null savepoints held within the current transaction;
26
     *              list: struct:
27
     *                'name' => savepoint name,
28
     *                'trans' => map: lower-cased names => original-cased names of properties the transaction-wide value
29
     *                           a change of which has been saved by this savepoint,
30
     *                'session' => similar for session-wide values;
31
     *              the list is sorted in the savepoint save order;
32
     *              the last struct always holds the currently unsaved values, its 'name' is null;
33
     *              <tt>null</tt> if not within transaction
34
     */
35
    private $bySavepoint;
36
37
    /** @var int index of the last struct in the $bySavepoint list */
38
    private $tailIdx;
39
40
41
    public function __construct(IObservableConnConfig $connConfig)
42
    {
43
        $this->connConfig = $connConfig;
44
    }
45
46
47
    public function handleSetForTransaction($propertyName)
48
    {
49
        if ($this->inTrans) {
50
            $this->bySavepoint[$this->tailIdx][self::SCOPE_TRANSACTION][strtolower($propertyName)] = $propertyName;
51
        }
52
    }
53
54
    public function handleSetForSession($propertyName)
55
    {
56
        if ($this->inTrans) {
57
            $this->bySavepoint[$this->tailIdx][self::SCOPE_SESSION][strtolower($propertyName)] = $propertyName;
58
        }
59
    }
60
61
    public function handleResetAll()
62
    {
63
        if ($this->inTrans) {
64
            $this->bySavepoint[$this->tailIdx][self::SCOPE_SESSION][self::RESET_ALL] = self::RESET_ALL;
65
        }
66
    }
67
68
69
70
    //region ITransactionControlObserver
71
72
    public function handleTransactionStart()
73
    {
74
        $this->inTrans = true;
75
        $this->tailIdx = 0;
76
        $this->bySavepoint = [$this->tailIdx => self::$emptyStruct];
77
    }
78
79
    public function handleTransactionCommit()
80
    {
81
        $props = [];
82
        assert($this->bySavepoint !== null, new InternalException('bySavepoint list should have been initialized'));
83
        foreach ($this->bySavepoint as $savepoint) {
84
            $props += $savepoint[self::SCOPE_TRANSACTION];
85
        }
86
        foreach ($props as $propName) {
87
            $this->connConfig->notifyPropertyChange($propName);
88
        }
89
90
        $this->inTrans = false;
91
        $this->bySavepoint = null;
92
        $this->tailIdx = null;
93
    }
94
95
    public function handleTransactionRollback()
96
    {
97
        $rolledBack = [];
98
        assert($this->bySavepoint !== null, new InternalException('bySavepoint list should have been initialized'));
99
        foreach ($this->bySavepoint as $savepoint) {
100
            $rolledBack += $savepoint[self::SCOPE_TRANSACTION];
101
            $rolledBack += $savepoint[self::SCOPE_SESSION];
102
        }
103
104 View Code Duplication
        if (isset($rolledBack[self::RESET_ALL])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
            $this->connConfig->notifyPropertiesReset();
106
        }
107
        else {
108
            foreach ($rolledBack as $propName) {
109
                $this->connConfig->notifyPropertyChange($propName);
110
            }
111
        }
112
113
        $this->inTrans = false;
114
        $this->bySavepoint = null;
115
        $this->tailIdx = null;
116
    }
117
118
    public function handleSavepointSaved($name)
119
    {
120
        $this->bySavepoint[$this->tailIdx]['name'] = $name;
121
        $this->tailIdx++;
122
        $this->bySavepoint[$this->tailIdx] = self::$emptyStruct;
123
    }
124
125
    public function handleSavepointReleased($name)
126
    {
127
        $idx = $this->findSavepoint($name);
128
        if ($idx === null) {
129
            return;
130
        }
131
132
        $unsaved = self::$emptyStruct;
133
        for ($i = $idx; $i <= $this->tailIdx; $i++) {
134
            $unsaved[self::SCOPE_TRANSACTION] += $this->bySavepoint[$i][self::SCOPE_TRANSACTION];
135
            $unsaved[self::SCOPE_SESSION] += $this->bySavepoint[$i][self::SCOPE_SESSION];
136
        }
137
        array_splice($this->bySavepoint, $idx, count($this->bySavepoint), [$unsaved]);
138
        $this->tailIdx = $idx;
139
    }
140
141
    public function handleRollbackToSavepoint($name)
142
    {
143
        $idx = $this->findSavepoint($name);
144
        if ($idx === null) {
145
            return;
146
        }
147
148
        $rolledBack = [];
149
        for ($i = $idx + 1; $i <= $this->tailIdx; $i++) {
150
            $rolledBack += $this->bySavepoint[$i][self::SCOPE_TRANSACTION];
151
            $rolledBack += $this->bySavepoint[$i][self::SCOPE_SESSION];
152
        }
153
        array_splice($this->bySavepoint, $idx + 1, count($this->bySavepoint), [self::$emptyStruct]);
154
        $this->tailIdx = $idx + 1;
155
156 View Code Duplication
        if (isset($rolledBack[self::RESET_ALL])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
157
            $this->connConfig->notifyPropertiesReset();
158
        }
159
        else {
160
            foreach ($rolledBack as $propName) {
161
                $this->connConfig->notifyPropertyChange($propName);
162
            }
163
        }
164
    }
165
166
    private function findSavepoint($name)
167
    {
168
        for ($idx = $this->tailIdx - 1; $idx >= 0; $idx--) {
169
            if ($this->bySavepoint[$idx]['name'] == $name) {
170
                return $idx;
171
            }
172
        }
173
        return null;
174
    }
175
176
    public function handleTransactionPrepared($name)
177
    {
178
        $this->handleTransactionCommit();
179
    }
180
181
    public function handlePreparedTransactionCommit($name)
182
    {
183
        // NOTE: does not affect the properties
184
    }
185
186
    public function handlePreparedTransactionRollback($name)
187
    {
188
        // NOTE: does not affect the properties
189
    }
190
191
    //endregion
192
}
193