├── README.md ├── composer.json ├── config └── database.php └── classes └── Database ├── MySQLi └── Result.php └── MySQLi.php /README.md: -------------------------------------------------------------------------------- 1 | kohana-3.3-mysqli 2 | ================= 3 | 4 | Kohana 3.3 MySQLi driver 5 | 6 | A simple substitution for the original MySQL driver. Just add as a module and set database driver to 'MySQLi'. 7 | Supports same functionality as the original driver. 8 | 9 | http://tomlankhorst.nl/mysqli-database-driver-for-kohana-3-3/ 10 | 11 | **Composer** 12 | ``` 13 | "require": { 14 | "tomlankhorst/kohana-3.3-mysqli": "dev-master", 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomlankhorst/kohana-3.3-mysqli", 3 | "type": "kohana-module", 4 | "description": "Kohana 3.3 MySQLi driver.", 5 | "homepage": "https://github.com/tomlankhorst/kohana-3.3-mysqli", 6 | "license": "BSD-3-Clause", 7 | "keywords": ["kohana", "databases", "mysql", "mysqli"], 8 | "authors": [ 9 | { 10 | "name": "Tom Lankhorst", 11 | "homepage": "http://tomlankhorst.nl/", 12 | "role": "developer" 13 | } 14 | ], 15 | "support": { 16 | "forum": "http://forum.kohanaframework.org", 17 | "irc": "irc://irc.freenode.net/kohana", 18 | "source": "https://github.com/tomlankhorst/kohana-3.3-mysqli" 19 | }, 20 | "require": { 21 | "kohana/database": "v3.3.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | array 6 | ( 7 | 'type' => 'MySQLi', 8 | 'connection' => array( 9 | /** 10 | * 11 | * string hostname server hostname 12 | * string database database name 13 | * string username database username 14 | * string password database password 15 | * string port server port 16 | * string socket connection socket 17 | * 18 | */ 19 | 'hostname' => 'localhost', 20 | 'database' => 'kohana', 21 | 'username' => FALSE, 22 | 'password' => FALSE, 23 | 'port' => NULL, 24 | 'socket' => NULL 25 | ), 26 | 'table_prefix' => '', 27 | 'charset' => 'utf8', 28 | 'caching' => FALSE, 29 | ) 30 | ); 31 | 32 | ?> -------------------------------------------------------------------------------- /classes/Database/MySQLi/Result.php: -------------------------------------------------------------------------------- 1 | _total_rows = mysqli_num_rows($result); 21 | } 22 | 23 | public function __destruct() 24 | { 25 | if (is_resource($this->_result)) 26 | { 27 | mysqli_free_result($this->_result); 28 | } 29 | } 30 | 31 | public function seek($offset) 32 | { 33 | if ($this->offsetExists($offset) AND mysqli_data_seek($this->_result, $offset)) 34 | { 35 | // Set the current row to the offset 36 | $this->_current_row = $this->_internal_row = $offset; 37 | 38 | return TRUE; 39 | } 40 | else 41 | { 42 | return FALSE; 43 | } 44 | } 45 | 46 | public function current() 47 | { 48 | if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) 49 | return NULL; 50 | 51 | // Increment internal row for optimization assuming rows are fetched in order 52 | $this->_internal_row++; 53 | 54 | if ($this->_as_object === TRUE) 55 | { 56 | // Return an stdClass 57 | return mysqli_fetch_object($this->_result); 58 | } 59 | elseif (is_string($this->_as_object)) 60 | { 61 | // Return an object of given class name 62 | return mysqli_fetch_object($this->_result, $this->_as_object); 63 | } 64 | else 65 | { 66 | // Return an array of the row 67 | return mysqli_fetch_assoc($this->_result); 68 | } 69 | } 70 | 71 | } // End Database_MySQLi_Result_Select 72 | -------------------------------------------------------------------------------- /classes/Database/MySQLi.php: -------------------------------------------------------------------------------- 1 | _connection) 28 | return; 29 | 30 | if (Database_MySQLi::$_set_names === NULL) 31 | { 32 | // Determine if we can use mysqli_set_charset(), which is only 33 | // available on PHP 5.2.3+ when compiled against MySQLi 5.0+ 34 | Database_MySQLi::$_set_names = ! function_exists('mysqli_set_charset'); 35 | } 36 | 37 | // Extract the connection parameters, adding required variabels 38 | extract($this->_config['connection'] + array( 39 | 'database' => '', 40 | 'hostname' => '', 41 | 'username' => '', 42 | 'password' => '', 43 | 'port' => null, 44 | 'socket' => '' 45 | )); 46 | 47 | // Prevent this information from showing up in traces 48 | unset($this->_config['connection']['username'], $this->_config['connection']['password']); 49 | 50 | try 51 | { 52 | $this->_connection = mysqli_connect($hostname, $username, $password, $database, $port, $socket); 53 | } 54 | catch (Exception $e) 55 | { 56 | // No connection exists 57 | $this->_connection = NULL; 58 | 59 | throw new Database_Exception(':error', 60 | array(':error' => $e->getMessage()), 61 | $e->getCode()); 62 | } 63 | 64 | // \xFF is a better delimiter, but the PHP driver uses underscore 65 | $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); 66 | 67 | $this->_select_db($database); 68 | 69 | if ( ! empty($this->_config['charset'])) 70 | { 71 | // Set the character set 72 | $this->set_charset($this->_config['charset']); 73 | } 74 | 75 | if ( ! empty($this->_config['connection']['variables'])) 76 | { 77 | // Set session variables 78 | $variables = array(); 79 | 80 | foreach ($this->_config['connection']['variables'] as $var => $val) 81 | { 82 | $variables[] = 'SESSION '.$var.' = '.$this->quote($val); 83 | } 84 | 85 | mysqli_query($this->_connection, 'SET '.implode(', ', $variables)); 86 | } 87 | } 88 | 89 | /** 90 | * Select the database 91 | * 92 | * @param string $database Database 93 | * @return void 94 | */ 95 | protected function _select_db($database) 96 | { 97 | // in MySQLi, the DB is already selected 98 | 99 | Database_MySQLi::$_current_databases[$this->_connection_id] = $database; 100 | } 101 | 102 | public function disconnect() 103 | { 104 | try 105 | { 106 | // Database is assumed disconnected 107 | $status = TRUE; 108 | 109 | if (is_resource($this->_connection)) 110 | { 111 | if ($status = mysqli_close($this->_connection)) 112 | { 113 | // Clear the connection 114 | $this->_connection = NULL; 115 | 116 | // Clear the instance 117 | parent::disconnect(); 118 | } 119 | } 120 | } 121 | catch (Exception $e) 122 | { 123 | // Database is probably not disconnected 124 | $status = ! is_resource($this->_connection); 125 | } 126 | 127 | return $status; 128 | } 129 | 130 | public function set_charset($charset) 131 | { 132 | // Make sure the database is connected 133 | $this->_connection or $this->connect(); 134 | 135 | if (Database_MySQLi::$_set_names === TRUE) 136 | { 137 | // PHP is compiled against MySQLi 4.x 138 | $status = (bool) mysqli_query($this->_connection, 'SET NAMES '.$this->quote($charset)); 139 | } 140 | else 141 | { 142 | // PHP is compiled against MySQLi 5.x 143 | $status = mysqli_set_charset($this->_connection, $charset); 144 | } 145 | 146 | if ($status === FALSE) 147 | { 148 | throw new Database_Exception(':error', 149 | array(':error' => mysqli_error($this->_connection)), 150 | mysqli_errno($this->_connection)); 151 | } 152 | } 153 | 154 | public function query($type, $sql, $as_object = FALSE, array $params = NULL) 155 | { 156 | // Make sure the database is connected 157 | $this->_connection or $this->connect(); 158 | 159 | if (Kohana::$profiling) 160 | { 161 | // Benchmark this query for the current instance 162 | $benchmark = Profiler::start("Database ({$this->_instance})", $sql); 163 | } 164 | 165 | // Execute the query 166 | if (($result = mysqli_query($this->_connection, $sql)) === FALSE) 167 | { 168 | if (isset($benchmark)) 169 | { 170 | // This benchmark is worthless 171 | Profiler::delete($benchmark); 172 | } 173 | 174 | throw new Database_Exception(':error [ :query ]', 175 | array(':error' => mysqli_error($this->_connection), ':query' => $sql), 176 | mysqli_errno($this->_connection)); 177 | } 178 | 179 | if (isset($benchmark)) 180 | { 181 | Profiler::stop($benchmark); 182 | } 183 | 184 | // Set the last query 185 | $this->last_query = $sql; 186 | 187 | if ($type === Database::SELECT) 188 | { 189 | // Return an iterator of results 190 | return new Database_MySQLi_Result($result, $sql, $as_object, $params); 191 | } 192 | elseif ($type === Database::INSERT) 193 | { 194 | // Return a list of insert id and rows created 195 | return array( 196 | mysqli_insert_id($this->_connection), 197 | mysqli_affected_rows($this->_connection), 198 | ); 199 | } 200 | else 201 | { 202 | // Return the number of rows affected 203 | return mysqli_affected_rows($this->_connection); 204 | } 205 | } 206 | 207 | public function datatype($type) 208 | { 209 | static $types = array 210 | ( 211 | 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'), 212 | 'bool' => array('type' => 'bool'), 213 | 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'), 214 | 'datetime' => array('type' => 'string'), 215 | 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 216 | 'double' => array('type' => 'float'), 217 | 'double precision unsigned' => array('type' => 'float', 'min' => '0'), 218 | 'double unsigned' => array('type' => 'float', 'min' => '0'), 219 | 'enum' => array('type' => 'string'), 220 | 'fixed' => array('type' => 'float', 'exact' => TRUE), 221 | 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 222 | 'float unsigned' => array('type' => 'float', 'min' => '0'), 223 | 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), 224 | 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), 225 | 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'), 226 | 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'), 227 | 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'), 228 | 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'), 229 | 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'), 230 | 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'), 231 | 'national varchar' => array('type' => 'string'), 232 | 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 233 | 'nvarchar' => array('type' => 'string'), 234 | 'point' => array('type' => 'string', 'binary' => TRUE), 235 | 'real unsigned' => array('type' => 'float', 'min' => '0'), 236 | 'set' => array('type' => 'string'), 237 | 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'), 238 | 'text' => array('type' => 'string', 'character_maximum_length' => '65535'), 239 | 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'), 240 | 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'), 241 | 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'), 242 | 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'), 243 | 'year' => array('type' => 'string'), 244 | ); 245 | 246 | $type = str_replace(' zerofill', '', $type); 247 | 248 | if (isset($types[$type])) 249 | return $types[$type]; 250 | 251 | return parent::datatype($type); 252 | } 253 | 254 | /** 255 | * Start a SQL transaction 256 | * 257 | * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html 258 | * 259 | * @param string $mode Isolation level 260 | * @return boolean 261 | */ 262 | public function begin($mode = NULL) 263 | { 264 | // Make sure the database is connected 265 | $this->_connection or $this->connect(); 266 | 267 | if ($mode AND ! mysqli_query($this->_connection, "SET TRANSACTION ISOLATION LEVEL $mode")) 268 | { 269 | throw new Database_Exception(':error', 270 | array(':error' => mysqli_error($this->_connection)), 271 | mysqli_errno($this->_connection)); 272 | } 273 | 274 | return (bool) mysqli_query($this->_connection, 'START TRANSACTION'); 275 | } 276 | 277 | /** 278 | * Commit a SQL transaction 279 | * 280 | * @return boolean 281 | */ 282 | public function commit() 283 | { 284 | // Make sure the database is connected 285 | $this->_connection or $this->connect(); 286 | 287 | return (bool) mysqli_query($this->_connection, 'COMMIT'); 288 | } 289 | 290 | /** 291 | * Rollback a SQL transaction 292 | * 293 | * @return boolean 294 | */ 295 | public function rollback() 296 | { 297 | // Make sure the database is connected 298 | $this->_connection or $this->connect(); 299 | 300 | return (bool) mysqli_query($this->_connection, 'ROLLBACK'); 301 | } 302 | 303 | public function list_tables($like = NULL) 304 | { 305 | if (is_string($like)) 306 | { 307 | // Search for table names 308 | $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); 309 | } 310 | else 311 | { 312 | // Find all table names 313 | $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); 314 | } 315 | 316 | $tables = array(); 317 | foreach ($result as $row) 318 | { 319 | $tables[] = reset($row); 320 | } 321 | 322 | return $tables; 323 | } 324 | 325 | public function list_columns($table, $like = NULL, $add_prefix = TRUE) 326 | { 327 | // Quote the table name 328 | $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; 329 | 330 | if (is_string($like)) 331 | { 332 | // Search for column names 333 | $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); 334 | } 335 | else 336 | { 337 | // Find all column names 338 | $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE); 339 | } 340 | 341 | $count = 0; 342 | $columns = array(); 343 | foreach ($result as $row) 344 | { 345 | list($type, $length) = $this->_parse_type($row['Type']); 346 | 347 | $column = $this->datatype($type); 348 | 349 | $column['column_name'] = $row['Field']; 350 | $column['column_default'] = $row['Default']; 351 | $column['data_type'] = $type; 352 | $column['is_nullable'] = ($row['Null'] == 'YES'); 353 | $column['ordinal_position'] = ++$count; 354 | 355 | switch ($column['type']) 356 | { 357 | case 'float': 358 | if (isset($length)) 359 | { 360 | list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); 361 | } 362 | break; 363 | case 'int': 364 | if (isset($length)) 365 | { 366 | // MySQLi attribute 367 | $column['display'] = $length; 368 | } 369 | break; 370 | case 'string': 371 | switch ($column['data_type']) 372 | { 373 | case 'binary': 374 | case 'varbinary': 375 | $column['character_maximum_length'] = $length; 376 | break; 377 | case 'char': 378 | case 'varchar': 379 | $column['character_maximum_length'] = $length; 380 | case 'text': 381 | case 'tinytext': 382 | case 'mediumtext': 383 | case 'longtext': 384 | $column['collation_name'] = $row['Collation']; 385 | break; 386 | case 'enum': 387 | case 'set': 388 | $column['collation_name'] = $row['Collation']; 389 | $column['options'] = explode('\',\'', substr($length, 1, -1)); 390 | break; 391 | } 392 | break; 393 | } 394 | 395 | // MySQLi attributes 396 | $column['comment'] = $row['Comment']; 397 | $column['extra'] = $row['Extra']; 398 | $column['key'] = $row['Key']; 399 | $column['privileges'] = $row['Privileges']; 400 | 401 | $columns[$row['Field']] = $column; 402 | } 403 | 404 | return $columns; 405 | } 406 | 407 | public function escape($value) 408 | { 409 | // Make sure the database is connected 410 | $this->_connection or $this->connect(); 411 | 412 | if (($value = mysqli_real_escape_string( $this->_connection, (string) $value)) === FALSE) 413 | { 414 | throw new Database_Exception(':error', 415 | array(':error' => mysqli_error($this->_connection)), 416 | mysqli_errno($this->_connection)); 417 | } 418 | 419 | // SQL standard is to use single-quotes for all values 420 | return "'$value'"; 421 | } 422 | 423 | } // End Database_MySQLi 424 | --------------------------------------------------------------------------------