Completed
Push — master ( 5f3c84...0c19f1 )
by Ondřej
03:04
created

TxConfig::createFromParams()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 3
eloc 14
nc 3
nop 3
1
<?php
2
declare(strict_types=1);
3
4
namespace Ivory\Connection;
5
6
use Ivory\Exception\InternalException;
7
8
/**
9
 * Transaction configuration. To be used, e.g., for {@link Connection::startTransaction()}.
10
 *
11
 * There are several configurable parameters of transactions in PostgreSQL. For each of them, a group of constants,
12
 * representing the possible values for the respective parameter, is available. Constants from distinct groups may be
13
 * combined together using the binary `|` operator.
14
 */
15
class TxConfig
16
{
17
    /** The `SERIALIZABLE` isolation level. */
18
    const ISOLATION_SERIALIZABLE = 1;
19
    /** The `REPEATABLE READ` isolation level. */
20
    const ISOLATION_REPEATABLE_READ = 2;
21
    /** The `READ COMMITTED` isolation level. */
22
    const ISOLATION_READ_COMMITTED = 4;
23
    /** The `READ UNCOMMITTED` isolation level. */
24
    const ISOLATION_READ_UNCOMMITTED = 8;
25
26
    /** The `READ WRITE` access mode. */
27
    const ACCESS_READ_WRITE = 128;
28
    /** The `READ ONLY` access mode. */
29
    const ACCESS_READ_ONLY = 256;
30
31
    /** The `DEFERRABLE` deferrable mode. Only effective in `SERIALIZABLE` `READ ONLY` transactions. */
32
    const DEFERRABLE = 4096;
33
    /** The `NOT DEFERRABLE` deferrable mode. */
34
    const NOT_DEFERRABLE = 8192;
35
36
37
    private $isolationLevel = null;
38
    private $readOnly = null;
39
    private $deferrable = null;
40
41
42
    public static function create($transactionOptions)
43
    {
44
        if ($transactionOptions instanceof TxConfig) {
45
            return $transactionOptions;
46
        } else {
47
            return new TxConfig($transactionOptions);
48
        }
49
    }
50
51
    public static function createFromParams(?string $isolationLevel, ?bool $readOnly, ?bool $deferrable): TxConfig
52
    {
53
        $txConf = new TxConfig();
54
        $txConf->setReadOnly($readOnly);
55
        $txConf->setDeferrable($deferrable);
56
57
        if ($isolationLevel !== null) {
58
            static $isolationLevels = [
59
                'serializable' => TxConfig::ISOLATION_SERIALIZABLE,
60
                'repeatable read' => TxConfig::ISOLATION_REPEATABLE_READ,
61
                'read committed' => TxConfig::ISOLATION_READ_COMMITTED,
62
                'read uncommitted' => TxConfig::ISOLATION_READ_UNCOMMITTED,
63
            ];
64
            if (!isset($isolationLevels[$isolationLevel])) {
65
                throw new \InvalidArgumentException("Unrecognized transaction isolation level: '$isolationLevel'");
66
            }
67
            $txConf->setIsolationLevel($isolationLevels[$isolationLevel]);
68
        }
69
70
        return $txConf;
71
    }
72
73
    /**
74
     * @param int $options one or more {@link TxConfig} constants combined together with the <tt>|</tt> operator
75
     */
76
    public function __construct(int $options = 0)
77
    {
78
        $isolationLevel = $options & (
79
                self::ISOLATION_SERIALIZABLE | self::ISOLATION_REPEATABLE_READ |
80
                self::ISOLATION_READ_COMMITTED | self::ISOLATION_READ_UNCOMMITTED
81
            );
82
        if ($isolationLevel != 0) {
83
            $this->setIsolationLevel($isolationLevel);
84
        }
85
86
        $this->setReadOnly(
87
            self::initBool($options, self::ACCESS_READ_ONLY, self::ACCESS_READ_WRITE, 'read/write and read-only access mode')
88
        );
89
        $this->setDeferrable(
90
            self::initBool($options, self::DEFERRABLE, self::NOT_DEFERRABLE, 'deferrable and non-deferrable mode')
91
        );
92
    }
93
94
    private static function initBool(int $options, int $trueBit, int $falseBit, string $errorDesc): ?bool
95
    {
96
        if ($options & $trueBit) {
97
            if ($options & $falseBit) {
98
                throw new \InvalidArgumentException(__CLASS__ . " options specify both $errorDesc");
99
            }
100
            return true;
101
        } elseif ($options & $falseBit) {
102
            return false;
103
        } else {
104
            return null;
105
        }
106
    }
107
108
    /**
109
     * @return int|null one of the <tt>ISOLATION_*</tt> constants, or <tt>null</tt> if no isolation level is specified
110
     */
111
    public function getIsolationLevel(): ?int
112
    {
113
        return $this->isolationLevel;
114
    }
115
116
    /**
117
     * @param int|null $isolationLevel one of the <tt>ISOLATION_*</tt> constants, or <tt>null</tt> to clear specifying
118
     *                                   the isolation level
119
     * @return $this
120
     */
121
    public function setIsolationLevel(?int $isolationLevel)
122
    {
123
        static $levels = [
124
            null,
125
            self::ISOLATION_SERIALIZABLE,
126
            self::ISOLATION_REPEATABLE_READ,
127
            self::ISOLATION_READ_COMMITTED,
128
            self::ISOLATION_READ_UNCOMMITTED,
129
        ];
130
        if (!in_array($isolationLevel, $levels)) {
131
            throw new \InvalidArgumentException('Invalid isolation level specification');
132
        }
133
134
        $this->isolationLevel = $isolationLevel;
135
        return $this;
136
    }
137
138
    /**
139
     * @return bool|null whether the access mode is specified as read-only, or <tt>null</tt> if no access mode is
140
     *                     specified
141
     */
142
    public function isReadOnly(): ?bool
143
    {
144
        return $this->readOnly;
145
    }
146
147
    public function setReadOnly(?bool $readOnly): TxConfig
148
    {
149
        $this->readOnly = $readOnly;
150
        return $this;
151
    }
152
153
    public function isDeferrable(): ?bool
154
    {
155
        return $this->deferrable;
156
    }
157
158
    public function setDeferrable(?bool $deferrable): TxConfig
159
    {
160
        $this->deferrable = $deferrable;
161
        return $this;
162
    }
163
164
    public function toSql(): string
165
    {
166
        $clauses = [];
167
168
        $il = $this->getIsolationLevel();
169
        if ($il !== null) {
170
            switch ($il) {
171
                case self::ISOLATION_SERIALIZABLE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
172
                    $clauses[] = 'ISOLATION LEVEL SERIALIZABLE';
173
                    break;
174
                case self::ISOLATION_REPEATABLE_READ:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
175
                    $clauses[] = 'ISOLATION LEVEL REPEATABLE READ';
176
                    break;
177
                case self::ISOLATION_READ_COMMITTED:
178
                    $clauses[] = 'ISOLATION LEVEL READ COMMITTED';
179
                    break;
180
                case self::ISOLATION_READ_UNCOMMITTED:
181
                    $clauses[] = 'ISOLATION LEVEL READ UNCOMMITTED';
182
                    break;
183
                default:
184
                    throw new InternalException('Undefined isolation level');
185
            }
186
        }
187
188
        $ro = $this->isReadOnly();
189
        if ($ro !== null) {
190
            $clauses[] = ($ro ? 'READ ONLY' : 'READ WRITE');
191
        }
192
193
        $d = $this->isDeferrable();
194
        if ($d !== null) {
195
            $clauses[] = ($d ? 'DEFERRABLE' : 'NOT DEFERRABLE');
196
        }
197
198
        return implode(' ', $clauses);
199
    }
200
}
201