Subversion Repositories oidplus

Rev

Rev 1116 | Rev 1130 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
635 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
635 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
635 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
635 daniel-mar 26
class OIDplusDatabaseConnectionMySQLi extends OIDplusDatabaseConnection {
27
        private $conn = null; // only with MySQLnd
28
        private $prepare_cache = array();
29
        private $last_error = null; // we need that because MySQL divides prepared statement errors and normal query errors, but we have only one "error()" method
30
 
1116 daniel-mar 31
        /**
32
         * @param string $sql
33
         * @param array|null $prepared_args
34
         * @return OIDplusQueryResultMySQL|OIDplusQueryResultMySQLNoNativeDriver
35
         * @throws OIDplusException
36
         */
37
        public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult {
635 daniel-mar 38
                $this->last_error = null;
39
                if (is_null($prepared_args)) {
40
                        $res = $this->conn->query($sql, MYSQLI_STORE_RESULT);
41
 
42
                        if ($res === false) {
43
                                $this->last_error = $this->conn->error;
44
                                throw new OIDplusSQLException($sql, $this->error());
45
                        } else {
46
                                return new OIDplusQueryResultMySQL($res);
47
                        }
48
                } else {
49
                        if (!is_array($prepared_args)) {
50
                                throw new OIDplusException(_L('"prepared_args" must be either NULL or an ARRAY.'));
51
                        }
52
 
53
                        foreach ($prepared_args as &$value) {
54
                                // MySQLi has problems converting "true/false" to the data type "tinyint(1)"
1116 daniel-mar 55
                                // It seems to be the same issue as in PDO reported 14 years ago at https://bugs.php.net/bug.php?id=57157
635 daniel-mar 56
                                if (is_bool($value)) $value = $value ? '1' : '0';
57
                        }
58
 
59
                        if (isset($this->prepare_cache[$sql])) {
60
                                $ps = $this->prepare_cache[$sql];
61
                        } else {
62
                                $ps = $this->conn->prepare($sql);
63
                                if (!$ps) {
64
                                        $this->last_error = $this->conn->error;
65
                                        throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error());
66
                                }
67
 
68
                                // Caching the prepared is very risky
69
                                // In PDO and ODBC we may not do it, because execute() will
70
                                // destroy the existing cursors.
71
                                // (test this with ./?goto=oid%3A1.3.6.1.4.1.37553.8.32488192274
72
                                // you will see that 2.999 is missing in the tree)
73
                                // But $ps->get_result() seems to "clone" the cursor,
74
                                // so that $ps->execute may be called a second time?!
75
                                // However, it only works with mysqlnd's get_result,
76
                                // not with OIDplusQueryResultMySQLNoNativeDriver
77
                                if (self::nativeDriverAvailable()) {
78
                                        $this->prepare_cache[$sql] = $ps;
79
                                }
80
                        }
81
 
82
                        self::bind_placeholder_vars($ps,$prepared_args);
83
                        if (!$ps->execute()) {
84
                                $this->last_error = mysqli_stmt_error($ps);
85
                                throw new OIDplusSQLException($sql, $this->error());
86
                        }
87
 
88
                        if (self::nativeDriverAvailable()) {
89
                                return new OIDplusQueryResultMySQL($ps->get_result());
90
                        } else {
91
                                return new OIDplusQueryResultMySQLNoNativeDriver($ps);
92
                        }
93
                }
94
        }
95
 
1116 daniel-mar 96
        /**
97
         * @return int
98
         */
635 daniel-mar 99
        public function insert_id(): int {
100
                return $this->conn->insert_id;
101
        }
102
 
1116 daniel-mar 103
        /**
104
         * @return string
105
         */
635 daniel-mar 106
        public function error(): string {
107
                $err = $this->last_error;
108
                if ($err == null) $err = '';
109
                return $err;
110
        }
111
 
1116 daniel-mar 112
        /**
113
         * @return void
114
         * @throws OIDplusConfigInitializationException
115
         * @throws OIDplusException
116
         */
635 daniel-mar 117
        protected function doConnect()/*: void*/ {
118
                if (!function_exists('mysqli_connect')) throw new OIDplusException(_L('PHP extension "%1" not installed','MySQLi'));
119
 
120
                // Try connecting to the database
121
                $host     = OIDplus::baseConfig()->getValue('MYSQL_HOST',     'localhost');
122
                $username = OIDplus::baseConfig()->getValue('MYSQL_USERNAME', 'root');
123
                $password = OIDplus::baseConfig()->getValue('MYSQL_PASSWORD', '');
124
                $database = OIDplus::baseConfig()->getValue('MYSQL_DATABASE', 'oidplus');
813 daniel-mar 125
                $socket   = OIDplus::baseConfig()->getValue('MYSQL_SOCKET',   '');
635 daniel-mar 126
                list($hostname,$port) = explode(':', $host.':'.ini_get("mysqli.default_port"));
127
                $port = intval($port);
1050 daniel-mar 128
                $this->conn = @new \mysqli($hostname, $username, $password, $database, $port, $socket);
635 daniel-mar 129
                if (!empty($this->conn->connect_error) || ($this->conn->connect_errno != 0)) {
130
                        $message = $this->conn->connect_error;
863 daniel-mar 131
                        throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message));
635 daniel-mar 132
                }
133
 
134
                $this->prepare_cache = array();
135
                $this->last_error = null;
136
 
137
                $this->query("SET NAMES 'utf8'");
138
        }
139
 
1116 daniel-mar 140
        /**
141
         * @return void
142
         */
635 daniel-mar 143
        protected function doDisconnect()/*: void*/ {
144
                $this->prepare_cache = array();
145
                if (!is_null($this->conn)) {
146
                        $this->conn->close();
147
                        $this->conn = null;
148
                }
149
        }
150
 
1116 daniel-mar 151
        /**
152
         * @var bool
153
         */
635 daniel-mar 154
        private $intransaction = false;
155
 
1116 daniel-mar 156
        /**
157
         * @return bool
158
         */
635 daniel-mar 159
        public function transaction_supported(): bool {
160
                return true;
161
        }
162
 
1116 daniel-mar 163
        /**
164
         * @return int
165
         */
635 daniel-mar 166
        public function transaction_level(): int {
167
                return $this->intransaction ? 1 : 0;
168
        }
169
 
1116 daniel-mar 170
        /**
171
         * @return void
172
         * @throws OIDplusException
173
         */
635 daniel-mar 174
        public function transaction_begin()/*: void*/ {
175
                if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
176
                $this->conn->autocommit(false);
177
                $this->conn->begin_transaction();
178
                $this->intransaction = true;
179
        }
180
 
1116 daniel-mar 181
        /**
182
         * @return void
183
         */
635 daniel-mar 184
        public function transaction_commit()/*: void*/ {
185
                $this->conn->commit();
186
                $this->conn->autocommit(true);
187
                $this->intransaction = false;
188
        }
189
 
1116 daniel-mar 190
        /**
191
         * @return void
192
         */
635 daniel-mar 193
        public function transaction_rollback()/*: void*/ {
194
                $this->conn->rollback();
195
                $this->conn->autocommit(true);
196
                $this->intransaction = false;
197
        }
198
 
1116 daniel-mar 199
        /**
200
         * @return string
201
         */
635 daniel-mar 202
        public function sqlDate(): string {
203
                return 'now()';
204
        }
205
 
1116 daniel-mar 206
        /**
207
         * @return bool
208
         * @throws OIDplusException
209
         */
635 daniel-mar 210
        public static function nativeDriverAvailable(): bool {
211
                return function_exists('mysqli_fetch_all') && (OIDplus::baseConfig()->getValue('MYSQL_FORCE_MYSQLND_SUPPLEMENT', false) === false);
212
        }
213
 
1116 daniel-mar 214
        /**
215
         * @param object $stmt
216
         * @param array $params
217
         * @return bool
218
         */
1128 daniel-mar 219
        private static function bind_placeholder_vars(&$stmt, array $params): bool {
220
                // Note: "object" is not a type-hint!
635 daniel-mar 221
                // Credit to: Dave Morgan
222
                // Code taken from: http://www.devmorgan.com/blog/2009/03/27/dydl-part-3-dynamic-binding-with-mysqli-php/
223
                //                  https://stackoverflow.com/questions/17219214/how-to-bind-in-mysqli-dynamically
224
                if ($params != null) {
225
                        $types = '';                        //initial sting with types
226
                        foreach ($params as $param) {        //for each element, determine type and add
227
                                if (is_int($param)) {
228
                                        $types .= 'i';              //integer
229
                                } elseif (is_float($param)) {
230
                                        $types .= 'd';              //double
231
                                } elseif (is_string($param)) {
232
                                        $types .= 's';              //string
233
                                } else {
234
                                        $types .= 'b';              //blob and unknown
235
                                }
236
                        }
237
 
238
                        $bind_names = array();
239
                        $bind_names[] = $types;             //first param needed is the type string, e.g.: 'issss'
240
 
241
                        for ($i=0; $i<count($params);$i++) {    //go through incoming params and added em to array
242
                                $bind_name = 'bind' . $i;       //give them an arbitrary name
243
                                $$bind_name = $params[$i];      //add the parameter to the variable variable
244
                                $bind_names[] = &$$bind_name;   //now associate the variable as an element in an array
245
                        }
246
 
247
                        //error_log("better_mysqli has params ".print_r($bind_names, 1));
248
                        //call the function bind_param with dynamic params
249
                        call_user_func_array(array($stmt,'bind_param'),$bind_names);
250
                        return true;
251
                } else {
252
                        return false;
253
                }
254
        }
255
 
1116 daniel-mar 256
        /**
257
         * @param bool $mustExist
258
         * @return OIDplusSqlSlangPlugin|null
259
         * @throws OIDplusConfigInitializationException
260
         */
635 daniel-mar 261
        protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
262
                $slang = OIDplus::getSqlSlangPlugin('mysql');
263
                if (is_null($slang)) {
264
                        throw new OIDplusConfigInitializationException(_L('SQL-Slang plugin "%1" is missing. Please check if it exists in the directory "plugin/sqlSlang". If it is not existing, please recover it from an SVN snapshot or OIDplus TAR.GZ file.','mysql'));
265
                }
266
                return $slang;
267
        }
268
}