DBAL::getLastErrorMessage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/** @noinspection PhpComposerExtensionStubsInspection */
4
5
declare(strict_types=1);
6
7
namespace EngineWorks\DBAL\Mysqli;
8
9
use EngineWorks\DBAL\Abstracts\BaseDBAL;
10
use EngineWorks\DBAL\Traits\MethodSqlConcatenate;
11
use EngineWorks\DBAL\Traits\MethodSqlLike;
12
use EngineWorks\DBAL\Traits\MethodSqlLimit;
13
use InvalidArgumentException;
14
use LogicException;
15
use mysqli;
16
use mysqli_driver;
17
use mysqli_result;
18
use RuntimeException;
19
20
/**
21
 * Mysqli implementation
22
 * @package EngineWorks\DBAL\Mysqli
23
 */
24
class DBAL extends BaseDBAL
25
{
26
    use MethodSqlLike;
27
    use MethodSqlLimit;
28
    use MethodSqlConcatenate;
29
30
    /**
31
     * Contains the connection resource for mysqli
32
     * @var mysqli|null
33
     */
34
    protected $mysqli = null;
35
36 69
    public function connect(): bool
37
    {
38
        // disconnect
39 69
        $this->disconnect();
40
        // create the mysqli object without error reporting
41 69
        $errorLevel = error_reporting(0);
42 69
        $mysqli = mysqli_init();
43 69
        if (! $mysqli instanceof mysqli) {
0 ignored issues
show
introduced by
$mysqli is always a sub-type of mysqli.
Loading history...
44
            error_reporting($errorLevel);
45
            throw new LogicException('Unable to create Mysqli empty object');
46
        }
47 69
        if (MYSQLI_REPORT_OFF !== (new mysqli_driver())->report_mode) {
48
            throw new RuntimeException('Mysqli error report mode should be MYSQLI_REPORT_OFF');
49
        }
50 69
        $this->mysqli = $mysqli;
51 69
        $connectTimeout = $this->settings->get('connect-timeout');
52 69
        if (null !== $connectTimeout) {
53 69
            $this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, (int) $connectTimeout);
54
        }
55 69
        $this->mysqli->real_connect(
56 69
            (string) $this->settings->get('host'),
57 69
            (string) $this->settings->get('user'),
58 69
            (string) $this->settings->get('password'),
59 69
            (string) $this->settings->get('database'),
60 69
            (int) $this->settings->get('port'),
61 69
            (string) $this->settings->get('socket'),
62 69
            (int) $this->settings->get('flags')
63 69
        );
64 69
        error_reporting($errorLevel);
65
        // check there are no connection errors
66 69
        if ($this->mysqli->connect_errno) {
67 1
            $errormsg = "Connection fail [{$this->mysqli->connect_errno}] {$this->mysqli->connect_error}";
68 1
            $this->logger->info('-- ' . $errormsg);
69 1
            $this->logger->error($errormsg);
70 1
            $this->mysqli = null;
71 1
            return false;
72
        }
73
        // OK, we are connected
74 68
        $this->logger->info('-- Connect and database select OK');
75
        // set encoding if needed
76 68
        $encoding = (string) $this->settings->get('encoding', '');
77 68
        if ('' !== $encoding) {
78 68
            $this->logger->info("-- Setting encoding to $encoding;");
79 68
            if (! $this->mysqli->set_charset($encoding)) {
80
                $this->logger->warning("-- Unable to set encoding to $encoding");
81
            }
82
        }
83 68
        return true;
84
    }
85
86 71
    public function disconnect(): void
87
    {
88 71
        if ($this->isConnected()) {
89 68
            $this->logger->info('-- Disconnection');
90 68
            $this->mysqli()->close();
91
        }
92 71
        $this->transactionLevel = 0;
93 71
        $this->mysqli = null;
94
    }
95
96 91
    public function isConnected(): bool
97
    {
98 91
        return ($this->mysqli instanceof mysqli);
99
    }
100
101
    public function lastInsertedID(): int
102
    {
103
        return (int) $this->mysqli()->insert_id;
104
    }
105
106 73
    public function sqlString($variable): string
107
    {
108 73
        if ($this->isConnected()) {
109 68
            return $this->mysqli()->escape_string(strval($variable));
110
        }
111
        // there are no function to escape without a link
112 5
        return str_replace(
113 5
            ['\\', "\0", "\n", "\r", "'", '"', "\x1a"],
114 5
            ['\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'],
115 5
            (string) $variable
116 5
        );
117
    }
118
119
    /**
120
     * Executes a query and return an object or resource native to the driver
121
     * This is the internal function to do the query according to the database functions
122
     * It's used by queryResult and queryAffectedRows methods
123
     * @param string $query
124
     * @return mysqli_result<mixed>|bool
125
     */
126 68
    protected function queryDriver(string $query)
127
    {
128 68
        $this->logger->debug($query);
129 68
        $result = $this->mysqli()->query($query);
130 68
        if (false === $result) {
131 11
            $this->logger->info("-- Query fail with SQL: $query");
132 11
            $this->logger->error("FAIL: $query\nLast message:" . $this->getLastMessage());
133
        }
134 68
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type true which is incompatible with the documented return type mysqli_result.
Loading history...
135
    }
136
137
    /** @return Result|false */
138 59
    public function queryResult(string $query, array $overrideTypes = [])
139
    {
140 59
        $result = $this->queryDriver($query);
141 59
        if ($result instanceof mysqli_result) {
0 ignored issues
show
introduced by
$result is always a sub-type of mysqli_result.
Loading history...
142 49
            return new Result($result, $overrideTypes);
143
        }
144 10
        if (true === $result) {
145
            $this->logger->warning("-- The query $query was executed but it does not return a result");
146
        }
147 10
        return false;
148
    }
149
150 68
    protected function queryAffectedRows(string $query)
151
    {
152 68
        if (false !== $this->queryDriver($query)) {
0 ignored issues
show
introduced by
The condition false !== $this->queryDriver($query) is always true.
Loading history...
153 68
            return max(0, (int) $this->mysqli()->affected_rows);
154
        }
155 1
        return false;
156
    }
157
158 11
    protected function getLastErrorMessage(): string
159
    {
160 11
        return '[' . $this->mysqli()->errno . '] ' . $this->mysqli()->error;
161
    }
162
163 5
    public function sqlTableEscape(string $tableName, string $asTable = ''): string
164
    {
165 5
        return '`' . $tableName . '`' . (('' !== $asTable) ? ' AS `' . $asTable . '`' : '');
166
    }
167
168 5
    public function sqlFieldEscape(string $fieldName, string $asTable = ''): string
169
    {
170 5
        return '`' . $fieldName . '`' . (('' !== $asTable) ? ' AS `' . $asTable . '`' : '');
171
    }
172
173 10
    public function sqlDatePart(string $part, string $expression): string
174
    {
175 10
        $format = $this->sqlDatePartFormat($part);
176 10
        return sprintf('DATE_FORMAT(%s, %s)', $expression, $this->sqlQuote($format, self::TTEXT));
177
    }
178
179 10
    private function sqlDatePartFormat(string $part): string
180
    {
181 10
        switch (strtoupper($part)) {
182 10
            case 'YEAR':
183 1
                return '%Y';
184 9
            case 'MONTH':
185 1
                return '%m';
186 8
            case 'FDOM':
187 1
                return '%Y-%m-01';
188 7
            case 'FYM':
189 1
                return '%Y-%m';
190 6
            case 'FYMD':
191 1
                return '%Y-%m-%d';
192 5
            case 'DAY':
193 1
                return '%d';
194 4
            case 'HOUR':
195 1
                return '%H';
196 3
            case 'MINUTE':
197 1
                return '%i';
198 2
            case 'SECOND':
199 1
                return '%s';
200 1
            case 'FHMS':
201 1
                return '%H:%i:%s';
202
            default:
203
                throw new InvalidArgumentException("Date part $part is not valid");
204
        }
205
    }
206
207 1
    public function sqlIf(string $condition, string $truePart, string $falsePart): string
208
    {
209 1
        return 'IF(' . $condition . ', ' . $truePart . ', ' . $falsePart . ')';
210
    }
211
212 1
    public function sqlLimit(string $query, int $requestedPage, int $recordsPerPage = 20): string
213
    {
214 1
        return $this->sqlLimitOffset($query, $requestedPage, $recordsPerPage);
215
    }
216
217 1
    public function sqlRandomFunc(): string
218
    {
219 1
        return 'RAND()';
220
    }
221
222
    /** @noinspection PhpMissingParentCallCommonInspection */
223 68
    protected function commandTransactionBegin(): void
224
    {
225 68
        $this->execute('START TRANSACTION', 'Cannot start transaction');
226
    }
227
228 68
    private function mysqli(): mysqli
229
    {
230 68
        if (null === $this->mysqli) {
231
            throw new RuntimeException('The current state of the connection is NULL');
232
        }
233 68
        return $this->mysqli;
234
    }
235
}
236