Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
mysqli.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Database
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * MySQLi database driver
14  *
15  * @package Joomla.Platform
16  * @subpackage Database
17  * @see http://php.net/manual/en/book.mysqli.php
18  * @since 12.1
19  */
21 {
22  /**
23  * The name of the database driver.
24  *
25  * @var string
26  * @since 12.1
27  */
28  public $name = 'mysqli';
29 
30  /**
31  * The character(s) used to quote SQL statement names such as table names or field names,
32  * etc. The child classes should define this as necessary. If a single character string the
33  * same character is used for both sides of the quoted name, else the first character will be
34  * used for the opening quote and the second for the closing quote.
35  *
36  * @var string
37  * @since 12.2
38  */
39  protected $nameQuote = '`';
40 
41  /**
42  * The null or zero representation of a timestamp for the database driver. This should be
43  * defined in child classes to hold the appropriate value for the engine.
44  *
45  * @var string
46  * @since 12.2
47  */
48  protected $nullDate = '0000-00-00 00:00:00';
49 
50  /**
51  * @var string The minimum supported database version.
52  * @since 12.2
53  */
54  protected static $dbMinimum = '5.0.4';
55 
56  /**
57  * Constructor.
58  *
59  * @param array $options List of options used to configure the connection
60  *
61  * @since 12.1
62  */
63  public function __construct($options)
64  {
65  // Get some basic values from the options.
66  $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
67  $options['user'] = (isset($options['user'])) ? $options['user'] : 'root';
68  $options['password'] = (isset($options['password'])) ? $options['password'] : '';
69  $options['database'] = (isset($options['database'])) ? $options['database'] : '';
70  $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;
71  $options['port'] = null;
72  $options['socket'] = null;
73 
74  // Finalize initialisation.
75  parent::__construct($options);
76  }
77 
78  /**
79  * Destructor.
80  *
81  * @since 12.1
82  */
83  public function __destruct()
84  {
85  $this->disconnect();
86  }
87 
88  /**
89  * Connects to the database if needed.
90  *
91  * @return void Returns void if the database connected successfully.
92  *
93  * @since 12.1
94  * @throws RuntimeException
95  */
96  public function connect()
97  {
98  if ($this->connection)
99  {
100  return;
101  }
102 
103  /*
104  * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we
105  * have to extract them from the host string.
106  */
107  $port = isset($this->options['port']) ? $this->options['port'] : 3306;
108 
109  if (preg_match('/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/', $this->options['host'], $matches))
110  {
111  // It's an IPv4 address with ot without port
112  $this->options['host'] = $matches['host'];
113 
114  if (!empty($matches['port']))
115  {
116  $port = $matches['port'];
117  }
118  }
119  elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $this->options['host'], $matches))
120  {
121  // We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306
122  $this->options['host'] = $matches['host'];
123 
124  if (!empty($matches['port']))
125  {
126  $port = $matches['port'];
127  }
128  }
129  elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $this->options['host'], $matches))
130  {
131  // Named host (e.g domain.com or localhost) with ot without port
132  $this->options['host'] = $matches['host'];
133 
134  if (!empty($matches['port']))
135  {
136  $port = $matches['port'];
137  }
138  }
139  elseif (preg_match('/^:(?P<port>[^:]+)$/', $this->options['host'], $matches))
140  {
141  // Empty host, just port, e.g. ':3306'
142  $this->options['host'] = 'localhost';
143  $port = $matches['port'];
144  }
145  // ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default
146 
147  // Get the port number or socket name
148  if (is_numeric($port))
149  {
150  $this->options['port'] = (int) $port;
151  }
152  else
153  {
154  $this->options['socket'] = $port;
155  }
156 
157  // Make sure the MySQLi extension for PHP is installed and enabled.
158  if (!function_exists('mysqli_connect'))
159  {
160  throw new RuntimeException('The MySQL adapter mysqli is not available');
161  }
162 
163  $this->connection = @mysqli_connect(
164  $this->options['host'], $this->options['user'], $this->options['password'], null, $this->options['port'], $this->options['socket']
165  );
166 
167  // Attempt to connect to the server.
168  if (!$this->connection)
169  {
170  throw new RuntimeException('Could not connect to MySQL.');
171  }
172 
173  // Set sql_mode to non_strict mode
174  mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';");
175 
176  // If auto-select is enabled select the given database.
177  if ($this->options['select'] && !empty($this->options['database']))
178  {
179  $this->select($this->options['database']);
180  }
181 
182  // Set charactersets (needed for MySQL 4.1.2+).
183  $this->setUTF();
184 
185  // Turn MySQL profiling ON in debug mode:
186  if ($this->debug && $this->hasProfiling())
187  {
188  mysqli_query($this->connection, "SET profiling_history_size = 100;");
189  mysqli_query($this->connection, "SET profiling = 1;");
190  }
191  }
192 
193  /**
194  * Disconnects the database.
195  *
196  * @return void
197  *
198  * @since 12.1
199  */
200  public function disconnect()
201  {
202  // Close the connection.
203  if ($this->connection)
204  {
205  foreach ($this->disconnectHandlers as $h)
206  {
207  call_user_func_array($h, array( &$this));
208  }
209 
210  mysqli_close($this->connection);
211  }
212 
213  $this->connection = null;
214  }
215 
216  /**
217  * Method to escape a string for usage in an SQL statement.
218  *
219  * @param string $text The string to be escaped.
220  * @param boolean $extra Optional parameter to provide extra escaping.
221  *
222  * @return string The escaped string.
223  *
224  * @since 12.1
225  */
226  public function escape($text, $extra = false)
227  {
228  $this->connect();
229 
230  $result = mysqli_real_escape_string($this->getConnection(), $text);
231 
232  if ($extra)
233  {
234  $result = addcslashes($result, '%_');
235  }
236 
237  return $result;
238  }
239 
240  /**
241  * Test to see if the MySQL connector is available.
242  *
243  * @return boolean True on success, false otherwise.
244  *
245  * @since 12.1
246  */
247  public static function isSupported()
248  {
249  return (function_exists('mysqli_connect'));
250  }
251 
252  /**
253  * Determines if the connection to the server is active.
254  *
255  * @return boolean True if connected to the database engine.
256  *
257  * @since 12.1
258  */
259  public function connected()
260  {
261  if (is_object($this->connection))
262  {
263  return mysqli_ping($this->connection);
264  }
265 
266  return false;
267  }
268 
269  /**
270  * Drops a table from the database.
271  *
272  * @param string $tableName The name of the database table to drop.
273  * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
274  *
275  * @return JDatabaseDriverMysqli Returns this object to support chaining.
276  *
277  * @since 12.2
278  * @throws RuntimeException
279  */
280  public function dropTable($tableName, $ifExists = true)
281  {
282  $this->connect();
283 
284  $query = $this->getQuery(true);
285 
286  $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));
287 
288  $this->execute();
289 
290  return $this;
291  }
292 
293  /**
294  * Get the number of affected rows for the previous executed SQL statement.
295  *
296  * @return integer The number of affected rows.
297  *
298  * @since 12.1
299  */
300  public function getAffectedRows()
301  {
302  $this->connect();
303 
304  return mysqli_affected_rows($this->connection);
305  }
306 
307  /**
308  * Method to get the database collation in use by sampling a text field of a table in the database.
309  *
310  * @return mixed The collation in use by the database (string) or boolean false if not supported.
311  *
312  * @since 12.2
313  * @throws RuntimeException
314  */
315  public function getCollation()
316  {
317  $this->connect();
318 
319  $tables = $this->getTableList();
320 
321  $this->setQuery('SHOW FULL COLUMNS FROM ' . $tables[0]);
322  $array = $this->loadAssocList();
323 
324  foreach ($array as $field)
325  {
326  if (!is_null($field['Collation']))
327  {
328  return $field['Collation'];
329  }
330  }
331 
332  return null;
333  }
334 
335  /**
336  * Get the number of returned rows for the previous executed SQL statement.
337  *
338  * @param resource $cursor An optional database cursor resource to extract the row count from.
339  *
340  * @return integer The number of returned rows.
341  *
342  * @since 12.1
343  */
344  public function getNumRows($cursor = null)
345  {
346  return mysqli_num_rows($cursor ? $cursor : $this->cursor);
347  }
348 
349  /**
350  * Shows the table CREATE statement that creates the given tables.
351  *
352  * @param mixed $tables A table name or a list of table names.
353  *
354  * @return array A list of the create SQL for the tables.
355  *
356  * @since 12.1
357  * @throws RuntimeException
358  */
359  public function getTableCreate($tables)
360  {
361  $this->connect();
362 
363  $result = array();
364 
365  // Sanitize input to an array and iterate over the list.
366  settype($tables, 'array');
367  foreach ($tables as $table)
368  {
369  // Set the query to get the table CREATE statement.
370  $this->setQuery('SHOW CREATE table ' . $this->quoteName($this->escape($table)));
371  $row = $this->loadRow();
372 
373  // Populate the result array based on the create statements.
374  $result[$table] = $row[1];
375  }
376 
377  return $result;
378  }
379 
380  /**
381  * Retrieves field information about a given table.
382  *
383  * @param string $table The name of the database table.
384  * @param boolean $typeOnly True to only return field types.
385  *
386  * @return array An array of fields for the database table.
387  *
388  * @since 12.2
389  * @throws RuntimeException
390  */
391  public function getTableColumns($table, $typeOnly = true)
392  {
393  $this->connect();
394 
395  $result = array();
396 
397  // Set the query to get the table fields statement.
398  $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table)));
399  $fields = $this->loadObjectList();
400 
401  // If we only want the type as the value add just that to the list.
402  if ($typeOnly)
403  {
404  foreach ($fields as $field)
405  {
406  $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
407  }
408  }
409  // If we want the whole field data object add that to the list.
410  else
411  {
412  foreach ($fields as $field)
413  {
414  $result[$field->Field] = $field;
415  }
416  }
417 
418  return $result;
419  }
420 
421  /**
422  * Get the details list of keys for a table.
423  *
424  * @param string $table The name of the table.
425  *
426  * @return array An array of the column specification for the table.
427  *
428  * @since 12.2
429  * @throws RuntimeException
430  */
431  public function getTableKeys($table)
432  {
433  $this->connect();
434 
435  // Get the details columns information.
436  $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table));
437  $keys = $this->loadObjectList();
438 
439  return $keys;
440  }
441 
442  /**
443  * Method to get an array of all tables in the database.
444  *
445  * @return array An array of all the tables in the database.
446  *
447  * @since 12.2
448  * @throws RuntimeException
449  */
450  public function getTableList()
451  {
452  $this->connect();
453 
454  // Set the query to get the tables statement.
455  $this->setQuery('SHOW TABLES');
456  $tables = $this->loadColumn();
457 
458  return $tables;
459  }
460 
461  /**
462  * Get the version of the database connector.
463  *
464  * @return string The database connector version.
465  *
466  * @since 12.1
467  */
468  public function getVersion()
469  {
470  $this->connect();
471 
472  return mysqli_get_server_info($this->connection);
473  }
474 
475  /**
476  * Method to get the auto-incremented value from the last INSERT statement.
477  *
478  * @return mixed The value of the auto-increment field from the last inserted row.
479  * If the value is greater than maximal int value, it will return a string.
480  *
481  * @since 12.1
482  */
483  public function insertid()
484  {
485  $this->connect();
486 
487  return mysqli_insert_id($this->connection);
488  }
489 
490  /**
491  * Locks a table in the database.
492  *
493  * @param string $table The name of the table to unlock.
494  *
495  * @return JDatabaseDriverMysqli Returns this object to support chaining.
496  *
497  * @since 12.2
498  * @throws RuntimeException
499  */
500  public function lockTable($table)
501  {
502  $this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute();
503 
504  return $this;
505  }
506 
507  /**
508  * Execute the SQL statement.
509  *
510  * @return mixed A database cursor resource on success, boolean false on failure.
511  *
512  * @since 12.1
513  * @throws RuntimeException
514  */
515  public function execute()
516  {
517  $this->connect();
518 
519  if (!is_object($this->connection))
520  {
521  JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
522  throw new RuntimeException($this->errorMsg, $this->errorNum);
523  }
524 
525  // Take a local copy so that we don't modify the original query and cause issues later
526  $query = $this->replacePrefix((string) $this->sql);
527 
528  if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
529  {
530  $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
531  }
532 
533  // Increment the query counter.
534  $this->count++;
535 
536  // Reset the error values.
537  $this->errorNum = 0;
538  $this->errorMsg = '';
539  $memoryBefore = null;
540 
541  // If debugging is enabled then let's log the query.
542  if ($this->debug)
543  {
544  // Add the query to the object queue.
545  $this->log[] = $query;
546 
547  JLog::add($query, JLog::DEBUG, 'databasequery');
548 
549  $this->timings[] = microtime(true);
550 
551  if (is_object($this->cursor))
552  {
553  // Avoid warning if result already freed by third-party library
554  @$this->freeResult();
555  }
556  $memoryBefore = memory_get_usage();
557  }
558 
559  // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
560  $this->cursor = @mysqli_query($this->connection, $query);
561 
562  if ($this->debug)
563  {
564  $this->timings[] = microtime(true);
565  if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
566  {
567  $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
568  }
569  else
570  {
571  $this->callStacks[] = debug_backtrace();
572  }
573  $this->callStacks[count($this->callStacks) - 1][0]['memory'] = array($memoryBefore, memory_get_usage(), is_object($this->cursor) ? $this->getNumRows() : null);
574  }
575 
576  // If an error occurred handle it.
577  if (!$this->cursor)
578  {
579  $this->errorNum = (int) mysqli_errno($this->connection);
580  $this->errorMsg = (string) mysqli_error($this->connection) . ' SQL=' . $query;
581 
582  // Check if the server was disconnected.
583  if (!$this->connected())
584  {
585  try
586  {
587  // Attempt to reconnect.
588  $this->connection = null;
589  $this->connect();
590  }
591  // If connect fails, ignore that exception and throw the normal exception.
592  catch (RuntimeException $e)
593  {
594  JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'databasequery');
595  throw new RuntimeException($this->errorMsg, $this->errorNum);
596  }
597 
598  // Since we were able to reconnect, run the query again.
599  return $this->execute();
600  }
601  // The server was not disconnected.
602  else
603  {
604  JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'databasequery');
605  throw new RuntimeException($this->errorMsg, $this->errorNum);
606  }
607  }
608 
609  return $this->cursor;
610  }
611 
612  /**
613  * Renames a table in the database.
614  *
615  * @param string $oldTable The name of the table to be renamed
616  * @param string $newTable The new name for the table.
617  * @param string $backup Not used by MySQL.
618  * @param string $prefix Not used by MySQL.
619  *
620  * @return JDatabaseDriverMysqli Returns this object to support chaining.
621  *
622  * @since 12.2
623  * @throws RuntimeException
624  */
625  public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
626  {
627  $this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->execute();
628 
629  return $this;
630  }
631 
632  /**
633  * Select a database for use.
634  *
635  * @param string $database The name of the database to select for use.
636  *
637  * @return boolean True if the database was successfully selected.
638  *
639  * @since 12.1
640  * @throws RuntimeException
641  */
642  public function select($database)
643  {
644  $this->connect();
645 
646  if (!$database)
647  {
648  return false;
649  }
650 
651  if (!mysqli_select_db($this->connection, $database))
652  {
653  throw new RuntimeException('Could not connect to database.');
654  }
655 
656  return true;
657  }
658 
659  /**
660  * Set the connection to use UTF-8 character encoding.
661  *
662  * @return boolean True on success.
663  *
664  * @since 12.1
665  */
666  public function setUTF()
667  {
668  $this->connect();
669 
670  return $this->connection->set_charset('utf8');
671  }
672 
673  /**
674  * Method to commit a transaction.
675  *
676  * @param boolean $toSavepoint If true, commit to the last savepoint.
677  *
678  * @return void
679  *
680  * @since 12.2
681  * @throws RuntimeException
682  */
683  public function transactionCommit($toSavepoint = false)
684  {
685  $this->connect();
686 
687  if (!$toSavepoint || $this->transactionDepth <= 1)
688  {
689  if ($this->setQuery('COMMIT')->execute())
690  {
691  $this->transactionDepth = 0;
692  }
693 
694  return;
695  }
696 
697  $this->transactionDepth--;
698  }
699 
700  /**
701  * Method to roll back a transaction.
702  *
703  * @param boolean $toSavepoint If true, rollback to the last savepoint.
704  *
705  * @return void
706  *
707  * @since 12.2
708  * @throws RuntimeException
709  */
710  public function transactionRollback($toSavepoint = false)
711  {
712  $this->connect();
713 
714  if (!$toSavepoint || $this->transactionDepth <= 1)
715  {
716  if ($this->setQuery('ROLLBACK')->execute())
717  {
718  $this->transactionDepth = 0;
719  }
720 
721  return;
722  }
723 
724  $savepoint = 'SP_' . ($this->transactionDepth - 1);
725  $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));
726 
727  if ($this->execute())
728  {
729  $this->transactionDepth--;
730  }
731  }
732 
733  /**
734  * Method to initialize a transaction.
735  *
736  * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
737  *
738  * @return void
739  *
740  * @since 12.2
741  * @throws RuntimeException
742  */
743  public function transactionStart($asSavepoint = false)
744  {
745  $this->connect();
746 
747  if (!$asSavepoint || !$this->transactionDepth)
748  {
749  if ($this->setQuery('START TRANSACTION')->execute())
750  {
751  $this->transactionDepth = 1;
752  }
753 
754  return;
755  }
756 
757  $savepoint = 'SP_' . $this->transactionDepth;
758  $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));
759 
760  if ($this->execute())
761  {
762  $this->transactionDepth++;
763  }
764  }
765 
766  /**
767  * Method to fetch a row from the result set cursor as an array.
768  *
769  * @param mixed $cursor The optional result set cursor from which to fetch the row.
770  *
771  * @return mixed Either the next row from the result set or false if there are no more rows.
772  *
773  * @since 12.1
774  */
775  protected function fetchArray($cursor = null)
776  {
777  return mysqli_fetch_row($cursor ? $cursor : $this->cursor);
778  }
779 
780  /**
781  * Method to fetch a row from the result set cursor as an associative array.
782  *
783  * @param mixed $cursor The optional result set cursor from which to fetch the row.
784  *
785  * @return mixed Either the next row from the result set or false if there are no more rows.
786  *
787  * @since 12.1
788  */
789  protected function fetchAssoc($cursor = null)
790  {
791  return mysqli_fetch_assoc($cursor ? $cursor : $this->cursor);
792  }
793 
794  /**
795  * Method to fetch a row from the result set cursor as an object.
796  *
797  * @param mixed $cursor The optional result set cursor from which to fetch the row.
798  * @param string $class The class name to use for the returned row object.
799  *
800  * @return mixed Either the next row from the result set or false if there are no more rows.
801  *
802  * @since 12.1
803  */
804  protected function fetchObject($cursor = null, $class = 'stdClass')
805  {
806  return mysqli_fetch_object($cursor ? $cursor : $this->cursor, $class);
807  }
808 
809  /**
810  * Method to free up the memory used for the result set.
811  *
812  * @param mixed $cursor The optional result set cursor from which to fetch the row.
813  *
814  * @return void
815  *
816  * @since 12.1
817  */
818  protected function freeResult($cursor = null)
819  {
820  mysqli_free_result($cursor ? $cursor : $this->cursor);
821  if ((! $cursor) || ($cursor === $this->cursor))
822  {
823  $this->cursor = null;
824  }
825  }
826 
827  /**
828  * Unlocks tables in the database.
829  *
830  * @return JDatabaseDriverMysqli Returns this object to support chaining.
831  *
832  * @since 12.1
833  * @throws RuntimeException
834  */
835  public function unlockTables()
836  {
837  $this->setQuery('UNLOCK TABLES')->execute();
838 
839  return $this;
840  }
841 
842  /**
843  * Internal function to check if profiling is available
844  *
845  * @return boolean
846  *
847  * @since 3.1.3
848  */
849  private function hasProfiling()
850  {
851  try
852  {
853  $res = mysqli_query($this->connection, "SHOW VARIABLES LIKE 'have_profiling'");
854  $row = mysqli_fetch_assoc($res);
855 
856  return isset($row);
857  }
858  catch (Exception $e)
859  {
860  return false;
861  }
862  }
863 }