From 87a0cadb187c4b394de689e3621b80d36d005614 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Sat, 26 Apr 2014 10:44:43 +0200 Subject: [PATCH] starts_with, notice backtraces, better database error reporting and return values --- base.php | 45 ++++++++++++++++++++++++++++++++++++++++++ include.exceptions.php | 2 ++ include.misc.php | 5 +++++ include.mysql.php | 37 ++++++++++++++++++++++++++++------ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/base.php b/base.php index 301f369..9f81d6b 100644 --- a/base.php +++ b/base.php @@ -102,6 +102,51 @@ if(!empty($cphp_config->autoloader)) spl_autoload_register('cphp_autoload_class'); } +/* https://stackoverflow.com/a/1159235/1332715 */ +set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext) { + if(!(error_reporting() & $errno)) + return; + switch($errno) { + case E_WARNING : + case E_USER_WARNING : + case E_STRICT : + case E_NOTICE : + case E_USER_NOTICE : + $type = 'warning'; + $fatal = false; + break; + default : + $type = 'fatal error'; + $fatal = true; + break; + } + $trace = array_reverse(debug_backtrace()); + array_pop($trace); + if(php_sapi_name() == 'cli') { + echo 'Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ':' . "\n"; + foreach($trace as $item) + echo ' ' . (isset($item['file']) ? $item['file'] : '') . ' ' . (isset($item['line']) ? $item['line'] : '') . ' calling ' . $item['function'] . '()' . "\n"; + } else { + echo '

' . "\n"; + echo ' Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ':' . "\n"; + echo '

    ' . "\n"; + foreach($trace as $item) + echo '
  1. ' . (isset($item['file']) ? $item['file'] : '') . ' ' . (isset($item['line']) ? $item['line'] : '') . ' calling ' . $item['function'] . '()
  2. ' . "\n"; + echo '
' . "\n"; + echo '

' . "\n"; + } + if(ini_get('log_errors')) { + $items = array(); + foreach($trace as $item) + $items[] = (isset($item['file']) ? $item['file'] : '') . ' ' . (isset($item['line']) ? $item['line'] : '') . ' calling ' . $item['function'] . '()'; + $message = 'Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ': ' . join(' | ', $items); + error_log($message); + } + if($fatal) + exit(1); +}); + + set_exception_handler(function($e){ /* Intentionally not using the templater here; any inner exceptions * cause serious debugging issues. Avoiding potential issues by just diff --git a/include.exceptions.php b/include.exceptions.php index 3e22d97..a3b6f6a 100644 --- a/include.exceptions.php +++ b/include.exceptions.php @@ -29,6 +29,8 @@ class PrototypeException extends BaseException {} class ConstructorException extends BaseException {} class MissingDataException extends BaseException {} class DatabaseException extends BaseException {} +class DatabaseDuplicateException extends DatabaseException {} +class DatabaseConstraintException extends DatabaseException {} class TypeException extends BaseException {} class DeprecatedException extends BaseException {} diff --git a/include.misc.php b/include.misc.php index fccc655..95803a8 100644 --- a/include.misc.php +++ b/include.misc.php @@ -371,6 +371,11 @@ function generate_pagination($min, $max, $current, $around, $start, $end) } } +function starts_with($haystack, $needle) +{ + return (substr($haystack, 0, strlen($needle)) == $needle); +} + function ends_with($haystack, $needle) { return (substr($haystack, -strlen($needle)) == $needle); diff --git a/include.mysql.php b/include.mysql.php index e71fe4b..f2d7f34 100644 --- a/include.mysql.php +++ b/include.mysql.php @@ -25,7 +25,7 @@ class CachedPDO extends PDO $query_hash = md5($query); $parameter_hash = md5(serialize($parameters)); $cache_hash = $query_hash . $parameter_hash; - + $return_object = new stdClass; if($expiry != 0 && $result = mc_get($cache_hash)) @@ -73,7 +73,7 @@ class CachedPDO extends PDO if($result = $statement->fetchAll(PDO::FETCH_ASSOC)) { if(count($result) > 0) - { + { if($expiry != 0) { mc_set($cache_hash, $result, $expiry); @@ -89,16 +89,41 @@ class CachedPDO extends PDO } else { - /* There were zero results. Return null instead of an object without results, to allow for statements - * of the form if($result = $database->CachedQuery()) . */ - return null; + $last_id = $this->lastInsertId(); + + if($last_id == "0" || !starts_with(strtoupper($query), "INSERT")) + { + /* There were zero results. Return null instead of an object without results, to allow for statements + * of the form if($result = $database->CachedQuery()) . */ + return null; + } + else + { + /* This was an INSERT query. Return the primary ID of the created row. */ + return $last_id; + } } } else { /* The query failed. */ $err = $statement->errorInfo(); - throw new DatabaseException("The query failed: {$err[2]}", 0, null, array('query' => $query, 'parameters' => $parameters)); + + if($err[0] == "23000") + { + if(starts_with($err[2], "Duplicate entry")) /* There does not seem to be a better way of doing this. */ + { + throw new DatabaseDuplicateException("The query failed because one of the keys was not unique: {$err[2]}", 0, null, array('query' => $query, 'parameters' => $parameters)); + } + else + { + throw new DatabaseConstraintException("The query violates a database constraint: {$err[2]}", 0, null, array('query' => $query, 'parameters' => $parameters)); + } + } + else + { + throw new DatabaseException("The query failed: {$err[2]}", 0, null, array('query' => $query, 'parameters' => $parameters)); + } } }