Elgg  Version 1.11
Database.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg;
4 
15 class Database {
16 
18  private $tablePrefix;
19 
21  private $dbLinks = array();
22 
24  private $queryCount = 0;
25 
37  private $queryCache = null;
38 
42  private $queryCacheSize = 50;
43 
59  private $delayedQueries = array();
60 
62  private $installed = false;
63 
65  private $config;
66 
68  private $logger;
69 
76  public function __construct(\Elgg\Database\Config $config, \Elgg\Logger $logger) {
77 
78  $this->logger = $logger;
79  $this->config = $config;
80 
81  $this->tablePrefix = $config->getTablePrefix();
82 
83  $this->enableQueryCache();
84  }
85 
98  public function getLink($type) {
99  if (isset($this->dbLinks[$type])) {
100  return $this->dbLinks[$type];
101  } else if (isset($this->dbLinks['readwrite'])) {
102  return $this->dbLinks['readwrite'];
103  } else {
104  $this->setupConnections();
105  return $this->getLink($type);
106  }
107  }
108 
118  public function setupConnections() {
119  if ($this->config->isDatabaseSplit()) {
120  $this->establishLink('read');
121  $this->establishLink('write');
122  } else {
123  $this->establishLink('readwrite');
124  }
125  }
126 
127 
139  public function establishLink($dblinkname = "readwrite") {
140 
141  $conf = $this->config->getConnectionConfig($dblinkname);
142 
143  // Connect to database
144  $this->dbLinks[$dblinkname] = mysql_connect($conf['host'], $conf['user'], $conf['password'], true);
145  if (!$this->dbLinks[$dblinkname]) {
146  $msg = "Elgg couldn't connect to the database using the given credentials. Check the settings file.";
147  throw new \DatabaseException($msg);
148  }
149 
150  if (!mysql_select_db($conf['database'], $this->dbLinks[$dblinkname])) {
151  $msg = "Elgg couldn't select the database '{$conf['database']}'. Please check that the database is created and you have access to it.";
152  throw new \DatabaseException($msg);
153  }
154 
155  // Set DB for UTF8
156  mysql_query("SET NAMES utf8", $this->dbLinks[$dblinkname]);
157  }
158 
175  public function getData($query, $callback = '') {
176  return $this->getResults($query, $callback, false);
177  }
178 
192  public function getDataRow($query, $callback = '') {
193  return $this->getResults($query, $callback, true);
194  }
195 
207  public function insertData($query) {
208 
209  $this->logger->log("DB query $query", \Elgg\Logger::INFO);
210 
211  $dblink = $this->getLink('write');
212 
213  $this->invalidateQueryCache();
214 
215  if ($this->executeQuery("$query", $dblink)) {
216  return mysql_insert_id($dblink);
217  }
218 
219  return false;
220  }
221 
233  public function updateData($query, $getNumRows = false) {
234 
235  $this->logger->log("DB query $query", \Elgg\Logger::INFO);
236 
237  $dblink = $this->getLink('write');
238 
239  $this->invalidateQueryCache();
240 
241  if ($this->executeQuery("$query", $dblink)) {
242  if ($getNumRows) {
243  return mysql_affected_rows($dblink);
244  } else {
245  return true;
246  }
247  }
248 
249  return false;
250  }
251 
262  public function deleteData($query) {
263 
264  $this->logger->log("DB query $query", \Elgg\Logger::INFO);
265 
266  $dblink = $this->getLink('write');
267 
268  $this->invalidateQueryCache();
269 
270  if ($this->executeQuery("$query", $dblink)) {
271  return mysql_affected_rows($dblink);
272  }
273 
274  return false;
275  }
276 
290  public function fingerprintCallback($callback) {
291  if (is_string($callback)) {
292  return $callback;
293  }
294  if (is_object($callback)) {
295  return spl_object_hash($callback) . "::__invoke";
296  }
297  if (is_array($callback)) {
298  if (is_string($callback[0])) {
299  return "{$callback[0]}::{$callback[1]}";
300  }
301  return spl_object_hash($callback[0]) . "::{$callback[1]}";
302  }
303  // this should not happen
304  return "";
305  }
306 
319  protected function getResults($query, $callback = null, $single = false) {
320 
321  // Since we want to cache results of running the callback, we need to
322  // need to namespace the query with the callback and single result request.
323  // https://github.com/elgg/elgg/issues/4049
324  $query_id = (int)$single . $query . '|';
325  if ($callback) {
326  $is_callable = is_callable($callback);
327  if ($is_callable) {
328  $query_id .= $this->fingerprintCallback($callback);
329  } else {
330  // TODO do something about invalid callbacks
331  $callback = null;
332  }
333  } else {
334  $is_callable = false;
335  }
336  // MD5 yields smaller mem usage for cache and cleaner logs
337  $hash = md5($query_id);
338 
339  // Is cached?
340  if ($this->queryCache) {
341  if (isset($this->queryCache[$hash])) {
342  $this->logger->log("DB query $query results returned from cache (hash: $hash)", \Elgg\Logger::INFO);
343  return $this->queryCache[$hash];
344  }
345  }
346 
347  $dblink = $this->getLink('read');
348  $return = array();
349 
350  if ($result = $this->executeQuery("$query", $dblink)) {
351  while ($row = mysql_fetch_object($result)) {
352  if ($is_callable) {
353  $row = call_user_func($callback, $row);
354  }
355 
356  if ($single) {
357  $return = $row;
358  break;
359  } else {
360  $return[] = $row;
361  }
362  }
363  }
364 
365  if (empty($return)) {
366  $this->logger->log("DB query $query returned no results.", \Elgg\Logger::INFO);
367  }
368 
369  // Cache result
370  if ($this->queryCache) {
371  $this->queryCache[$hash] = $return;
372  $this->logger->log("DB query $query results cached (hash: $hash)", \Elgg\Logger::INFO);
373  }
374 
375  return $return;
376  }
377 
391  public function executeQuery($query, $dblink) {
392 
393  if ($query == null) {
394  throw new \DatabaseException("Query cannot be null");
395  }
396 
397  if (!is_resource($dblink)) {
398  throw new \DatabaseException("Connection to database was lost.");
399  }
400 
401  $this->queryCount++;
402 
403  $result = mysql_query($query, $dblink);
404 
405  if (mysql_errno($dblink)) {
406  throw new \DatabaseException(mysql_error($dblink) . "\n\n QUERY: $query");
407  }
408 
409  return $result;
410  }
411 
436  public function runSqlScript($scriptlocation) {
437  $script = file_get_contents($scriptlocation);
438  if ($script) {
439 
440  $errors = array();
441 
442  // Remove MySQL '-- ' and '# ' style comments
443  $script = preg_replace('/^(?:--|#) .*$/m', '', $script);
444 
445  // Statements must end with ; and a newline
446  $sql_statements = preg_split('/;[\n\r]+/', "$script\n");
447 
448  foreach ($sql_statements as $statement) {
449  $statement = trim($statement);
450  $statement = str_replace("prefix_", $this->tablePrefix, $statement);
451  if (!empty($statement)) {
452  try {
453  $this->updateData($statement);
454  } catch (\DatabaseException $e) {
455  $errors[] = $e->getMessage();
456  }
457  }
458  }
459  if (!empty($errors)) {
460  $errortxt = "";
461  foreach ($errors as $error) {
462  $errortxt .= " {$error};";
463  }
464 
465  $msg = "There were a number of issues: " . $errortxt;
466  throw new \DatabaseException($msg);
467  }
468  } else {
469  $msg = "Elgg couldn't find the requested database script at " . $scriptlocation . ".";
470  throw new \DatabaseException($msg);
471  }
472  }
473 
487  public function registerDelayedQuery($query, $type, $handler = "") {
488 
489  if (!is_resource($type) && $type != 'read' && $type != 'write') {
490  return false;
491  }
492 
493  // Construct delayed query
494  $delayed_query = array();
495  $delayed_query['q'] = $query;
496  $delayed_query['l'] = $type;
497  $delayed_query['h'] = $handler;
498 
499  $this->delayedQueries[] = $delayed_query;
500 
501  return true;
502  }
503 
504 
513  public function executeDelayedQueries() {
514 
515  foreach ($this->delayedQueries as $query_details) {
516  try {
517  $link = $query_details['l'];
518 
519  if ($link == 'read' || $link == 'write') {
520  $link = $this->getLink($link);
521  } elseif (!is_resource($link)) {
522  $msg = "Link for delayed query not valid resource or db_link type. Query: {$query_details['q']}";
523  $this->logger->log($msg, \Elgg\Logger::WARNING);
524  }
525 
526  $result = $this->executeQuery($query_details['q'], $link);
527 
528  if ((isset($query_details['h'])) && (is_callable($query_details['h']))) {
529  $query_details['h']($result);
530  }
531  } catch (\DatabaseException $e) {
532  // Suppress all exceptions since page already sent to requestor
533  $this->logger->log($e, \Elgg\Logger::ERROR);
534  }
535  }
536  }
537 
545  public function enableQueryCache() {
546  if ($this->config->isQueryCacheEnabled() && $this->queryCache === null) {
547  // @todo if we keep this cache, expose the size as a config parameter
548  $this->queryCache = new \Elgg\Cache\LRUCache($this->queryCacheSize);
549  }
550  }
551 
560  public function disableQueryCache() {
561  $this->queryCache = null;
562  }
563 
569  protected function invalidateQueryCache() {
570  if ($this->queryCache) {
571  $this->queryCache->clear();
572  $this->logger->log("Query cache invalidated", \Elgg\Logger::INFO);
573  }
574  }
575 
582  public function assertInstalled() {
583 
584  if ($this->installed) {
585  return;
586  }
587 
588  try {
589  $dblink = $this->getLink('read');
590  mysql_query("SELECT value FROM {$this->tablePrefix}datalists WHERE name = 'installed'", $dblink);
591  if (mysql_errno($dblink) > 0) {
592  throw new \DatabaseException();
593  }
594  } catch (\DatabaseException $e) {
595  throw new \InstallationException("Unable to handle this request. This site is not configured or the database is down.");
596  }
597 
598  $this->installed = true;
599  }
600 
606  public function getQueryCount() {
607  return $this->queryCount;
608  }
609 
615  public function getTablePrefix() {
616  return $this->tablePrefix;
617  }
618 
626  public function sanitizeInt($value, $signed = true) {
627  $value = (int) $value;
628 
629  if ($signed === false) {
630  if ($value < 0) {
631  $value = 0;
632  }
633  }
634 
635  return $value;
636  }
637 
644  public function sanitizeString($value) {
645 
646  // use resource if established, but don't open a connection to do it.
647  if (isset($this->dbLinks['read'])) {
648  return mysql_real_escape_string($value, $this->dbLinks['read']);
649  }
650 
651  return mysql_real_escape_string($value);
652  }
653 
661  public function getServerVersion($type) {
662  return mysql_get_server_info($this->getLink($type));
663  }
664 }
665 
if(!$owner||!($owner instanceof ElggUser)||!$owner->canEdit()) $error
Definition: upload.php:14
getData($query, $callback= '')
Retrieve rows from the database.
Definition: Database.php:175
getQueryCount()
Get the number of queries made to the database.
Definition: Database.php:606
getDataRow($query, $callback= '')
Retrieve a single row from the database.
Definition: Database.php:192
getLink($type)
Gets (if required, also creates) a database link resource.
Definition: Database.php:98
invalidateQueryCache()
Invalidate the query cache.
Definition: Database.php:569
$e
Definition: metadata.php:12
$CONFIG installed
Is the site fully installed.
Definition: config.php:115
deleteData($query)
Delete data from the database.
Definition: Database.php:262
$value
Definition: longtext.php:26
$return
Definition: opendd.php:15
executeQuery($query, $dblink)
Execute a query.
Definition: Database.php:391
insertData($query)
Insert a row into the database.
Definition: Database.php:207
sanitizeInt($value, $signed=true)
Sanitizes an integer value for use in a query.
Definition: Database.php:626
Save menu items.
sanitizeString($value)
Sanitizes a string for use in a query.
Definition: Database.php:644
assertInstalled()
Test that the Elgg database is installed.
Definition: Database.php:582
$type
Definition: add.php:8
runSqlScript($scriptlocation)
Runs a full database script from disk.
Definition: Database.php:436
registerDelayedQuery($query, $type, $handler="")
Queue a query for execution upon shutdown.
Definition: Database.php:487
fingerprintCallback($callback)
Get a string that uniquely identifies a callback during the current request.
Definition: Database.php:290
__construct(\Elgg\Database\Config $config,\Elgg\Logger $logger)
Constructor.
Definition: Database.php:76
updateData($query, $getNumRows=false)
Update the database.
Definition: Database.php:233
disableQueryCache()
Disable the query cache.
Definition: Database.php:560
enableQueryCache()
Enable the query cache.
Definition: Database.php:545
getResults($query, $callback=null, $single=false)
Handles queries that return results, running the results through a an optional callback function...
Definition: Database.php:319
establishLink($dblinkname="readwrite")
Establish a connection to the database server.
Definition: Database.php:139
elgg ajax ERROR
Definition: ajax.js:33
$row
getServerVersion($type)
Get the server version number.
Definition: Database.php:661
$handler
Definition: add.php:10
executeDelayedQueries()
Trigger all queries that were registered as "delayed" queries.
Definition: Database.php:513
setupConnections()
Establish database connections.
Definition: Database.php:118
getTablePrefix()
Get the prefix for Elgg&#39;s tables.
Definition: Database.php:615