Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
Select
n/a
0 / 0
n/a
0 / 0
39
n/a
0 / 0
 sanitizeStackTrace
n/a
0 / 0
n/a
0 / 0
1
 createPdoConnection
n/a
0 / 0
n/a
0 / 0
2
 setReadConnection
n/a
0 / 0
n/a
0 / 0
2
 getReadConnection
n/a
0 / 0
n/a
0 / 0
5
 hasReadConnection
n/a
0 / 0
n/a
0 / 0
2
 closeReadConnection
n/a
0 / 0
n/a
0 / 0
1
 setWriteConnection
n/a
0 / 0
n/a
0 / 0
2
 getWriteConnection
n/a
0 / 0
n/a
0 / 0
8
 hasWriteConnection
n/a
0 / 0
n/a
0 / 0
2
 closeWriteConnection
n/a
0 / 0
n/a
0 / 0
1
 setQueryCache
n/a
0 / 0
n/a
0 / 0
1
 setProfiling
n/a
0 / 0
n/a
0 / 0
1
 setCriticalReadSession
n/a
0 / 0
n/a
0 / 0
1
 setTransaction
n/a
0 / 0
n/a
0 / 0
1
 writeRollback
n/a
0 / 0
n/a
0 / 0
3
 writeCommit
n/a
0 / 0
n/a
0 / 0
4
 writeCommitWithStartLock
n/a
0 / 0
n/a
0 / 0
2
1<?php
2
3namespace BO\Zmsbackend\Connection;
4
5use BO\Slim\Helper\Sanitizer;
6
7/**
8 *
9 * @codeCoverageIgnore
10 *
11 * @SuppressWarnings(TooManyFields)
12 * Handle read and write connections
13 */
14class Select
15{
16    /**
17     * @var Bool $enableProfiling
18     */
19    public static $enableProfiling = false;
20
21    /**
22     * @var String $readSourceName PDO connection string
23     */
24    public static $readSourceName = null;
25
26    /**
27     * @var String $writeSourceName PDO connection string
28     */
29    public static $writeSourceName = null;
30
31    /**
32     * @var String $dbname_zms
33     */
34    public static $dbname_zms = 'zmsbo';
35
36    /**
37     * @var String $username Login
38     */
39    public static $username = null;
40
41    /**
42     * @var String $password Credential
43     */
44    public static $password = null;
45
46    /**
47     * @var Array $pdoOptions compatible to the 4th PDO::__construct parameter
48     */
49    public static $pdoOptions = [];
50
51    /**
52     * @var String $connectionTimezone
53     *
54     */
55    public static $connectionTimezone = ' UTC';
56
57    /**
58     * @var Bool $enableWsrepSyncWait
59     */
60    public static $enableWsrepSyncWait = false;
61
62    /**
63     * @var Bool $enableWsrepSyncWait
64     */
65    public static $galeraConnection = false;
66
67    /**
68     * @var Pdo|null $readConnection for read only requests
69     */
70    protected static $readConnection = null;
71
72    /**
73     * @var Pdo|null $writeConnection for write only requests
74     */
75    protected static $writeConnection = null;
76
77    /**
78     * @var object|null $readProfiler for read only requests
79     */
80    protected static $readProfiler = null;
81
82    /**
83     * @var object|null $writeProfiler for write only requests
84     */
85    protected static $writeProfiler = null;
86
87    /**
88     * @var Bool $useTransaction
89     *
90     */
91    protected static $useTransaction = false;
92
93    /**
94     * @var Bool $useProfiling
95     *
96     */
97    protected static $useProfiling = false;
98
99    /**
100     * @var Bool $useQueryCache
101     *
102     */
103    protected static $useQueryCache = true;
104
105    protected static function sanitizeStackTrace(string $trace)
106    {
107        return Sanitizer::sanitizeStackTrace($trace);
108    }
109
110    /**
111     * Create a PDO compatible object
112     *
113     * @param  String $dataSourceName compatible with PDO
114     */
115    protected static function createPdoConnection($dataSourceName): Pdo
116    {
117        try {
118            $pdoOptions = array_merge([
119                ], self::$pdoOptions);
120            $pdo = new Pdo($dataSourceName, self::$username, self::$password, $pdoOptions);
121            $pdo->exec('SET NAMES "UTF8";');
122            //$timezone = date_default_timezone_get();
123            //$pdo->prepare('SET time_zone = ?;')->execute([$timezone]);
124            $pdo->exec('SET SESSION sql_mode = "STRICT_ALL_TABLES";');
125            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
126        } catch (\Exception $exception) {
127            $sanitizedDsn     = self::sanitizeStackTrace($dataSourceName);
128            $sanitizedMessage = self::sanitizeStackTrace($exception->getMessage());
129            throw new \BO\Zmsbackend\Exception\Pdo\PDOFailed(
130                $sanitizedDsn . ': ' . $sanitizedMessage,
131            );
132        }
133        return $pdo;
134    }
135
136    /**
137     * Set the read connection.
138     * Usually this function is only required to set mockups for testing
139     *
140     * @param PdoInterface $connection
141     *
142     * @psalm-api
143     *
144     * @return void
145     */
146    public static function setReadConnection(PdoInterface $connection)
147    {
148        if (!$connection instanceof Pdo) {
149            throw new \InvalidArgumentException('Expected ' . Pdo::class);
150        }
151        self::$readConnection = $connection;
152    }
153
154    /**
155     * Create or return a connection for reading data
156     */
157    public static function getReadConnection(): Pdo
158    {
159        if (null === self::$readConnection) {
160            self::$readConnection = self::createPdoConnection(self::$readSourceName);
161            self::$readProfiler = new \Aura\Sql\Profiler\Profiler();
162            self::$readProfiler->setActive(self::$enableProfiling);
163            self::$readConnection->setProfiler(self::$readProfiler);
164            //self::$readConnection->exec('SET SESSION TRANSACTION READ ONLY');
165            if (!self::$useQueryCache) {
166                try {
167                    self::$readConnection->exec('SET SESSION query_cache_type = 0;');
168                } catch (\Exception $exception) {
169                    // ignore, query cache might be disabled
170                }
171            }
172            if (self::$useProfiling) {
173                self::$readConnection->exec('SET profiling = 1;');
174            }
175        }
176        return self::$readConnection;
177    }
178
179    /**
180     * Test if a read connection is established
181     */
182    public static function hasReadConnection(): bool
183    {
184        return (null === self::$readConnection) ? false : true;
185    }
186
187    /**
188     * Close a connection for reading data
189     */
190    public static function closeReadConnection(): void
191    {
192        self::$readConnection = null;
193    }
194
195    /**
196     * Set the write connection.
197     * Usually this function is only required to set mockups for testing
198     *
199     * @param  PdoInterface $connection
200     * @return self
201     * @psalm-api
202     */
203    public static function setWriteConnection(PdoInterface $connection)
204    {
205        if (!$connection instanceof Pdo) {
206            throw new \InvalidArgumentException('Expected ' . Pdo::class);
207        }
208        self::$writeConnection = $connection;
209    }
210
211    /**
212     * Create or return a connection for writing data
213     */
214    public static function getWriteConnection(): Pdo
215    {
216        if (null === self::$writeConnection) {
217            self::$writeConnection = self::createPdoConnection(self::$writeSourceName);
218            self::$writeProfiler = new \Aura\Sql\Profiler\Profiler();
219            self::$writeProfiler->setActive(self::$enableProfiling);
220            self::$writeConnection->setProfiler(self::$writeProfiler);
221            if (self::$useTransaction) {
222                self::$writeConnection->exec('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED');
223                self::$writeConnection->exec('SET SESSION innodb_lock_wait_timeout=15');
224                self::$writeConnection->beginTransaction();
225            }
226            if (!self::$useQueryCache) {
227                try {
228                    self::$writeConnection->exec('SET SESSION query_cache_type = 0;');
229                } catch (\Exception $exception) {
230                    // ignore, query cache might be disabled
231                }
232            }
233            if (self::$useProfiling) {
234                self::$writeConnection->exec('SET profiling = 1;');
235            }
236            if (self::$galeraConnection && self::$enableWsrepSyncWait) {
237                self::$writeConnection->exec(
238                    'SET SESSION wsrep_sync_wait = (
239                        SELECT CAST(value AS INT) FROM config WHERE name = "setting__wsrepsync"
240                    );'
241                );
242            }
243            // On writing, use the same host to avoid racing/transaction conditions
244            self::$readConnection = self::$writeConnection;
245        }
246        return self::$writeConnection;
247    }
248
249    /**
250     * Test if a write connection is established
251     */
252    public static function hasWriteConnection(): bool
253    {
254        return (null === self::$writeConnection) ? false : true;
255    }
256
257    /**
258     * Close a connection for writing data
259     */
260    public static function closeWriteConnection(): void
261    {
262        self::$writeConnection = null;
263    }
264
265    /**
266     * Set query cache
267     *
268     * @param Bool $useQueryCache
269     */
270    public static function setQueryCache($useQueryCache = true): void
271    {
272        static::$useQueryCache = $useQueryCache;
273    }
274
275    /**
276     * Set profiling
277     *
278     * @param Bool $useProfiling
279     */
280    public static function setProfiling($useProfiling = true): void
281    {
282        static::$useProfiling = $useProfiling;
283    }
284
285    /**
286     * Set cluster wide causality checks, needed for critical reads across different nodes
287     *
288     * @param Bool $wsrepStatus Set to true for critical reads
289     */
290    public static function setCriticalReadSession($wsrepStatus = true): void
291    {
292        static::$enableWsrepSyncWait = $wsrepStatus;
293        static::getWriteConnection();
294    }
295
296    /**
297     * Set transaction
298     *
299     * @param Bool $useTransaction
300     */
301    public static function setTransaction($useTransaction = true): void
302    {
303        static::$useTransaction = $useTransaction;
304    }
305
306    /**
307     * Rollback transaction if started
308     */
309    public static function writeRollback(): bool|null
310    {
311        if (self::$useTransaction && self::getWriteConnection()->inTransaction()) {
312            return self::getWriteConnection()->rollBack();
313        }
314        return null;
315    }
316
317    /**
318     * Commit transaction if started
319     */
320    public static function writeCommit(): bool|null
321    {
322        if (self::$useTransaction && null !== self::$writeConnection && self::getWriteConnection()->inTransaction()) {
323            $status = self::getWriteConnection()->commit();
324            self::$writeConnection->beginTransaction();
325            return $status;
326        }
327        return null;
328    }
329
330    public static function writeCommitWithStartLock(): bool
331    {
332        return self::writeCommit() && (new \BO\Zmsbackend\Config\Service\Config())->readProperty('status__calculateSlotsLastRun', true);
333    }
334}