Reputation: 69
I have encountered an issue with using a combination of the following in PHP:
All of this works fine; the classloader loads classes, the DB class executes queries correctly and the errorhandler logs the errors in the database.
...Except when an error occurs before any database operations have been called yet. In this case PHP crashes and crashes hard; no error messages or anything, not even any echo's or var_dump's, not even 4xx or 5xx codes. The browser just reports that nothing has been recieved.
There are a number of "fixes" I've found:
I can still autoload other classes inside the ErrorHandler, but the moment I instantiate "DB", PHP seems to crash.
From what I see, the only reasonable explanation seems to be that something in mysqli has issues with the scope of being first instantiated inside the ErrorHandler class, since all fixes that work seem to share the scope aspect, but that doesn't seem to make any sense at all.
Does anybody know what's going on here?
ClassLoader...
class ClassLoader {
private $_paths = array();
private function __construct() {
// ... bunch of $this->append() calls with all paths and 3rd party libs
}
private static $_instance = null;
public static function get() {
if (self::$_instance === null) {
self::$_instance = new self();
}
return self::$_instance;
}
public function append($path, $format = '$.class.php') {
if (!array_key_exists($path, $this->_paths)) {
$this->_paths[$path] = $format;
return true;
}
return false;
}
public function autoload($class_name) {
foreach ($this->_paths as $path => $format) {
$file = $path.'/'.str_replace('$', $class_name, $format);
if (file_exists($file)) {
require_once($file);
return true;
}
}
return false;
}
}
$autoloader = ClassLoader::get();
$autoloader->append(dirname(__FILE__).'/classes');
spl_autoload_register(array($autoloader, 'autoload'));
DB...
class DB extends mysqli {
private static $_instances = array();
public static function get(Config $config) {
$hash = md5(serialize($config));
if (!array_key_exists($hash, self::$_instances)) {
self::$_instances[$hash] = new self($config);
}
return self::$_instances[$hash];
}
private $_prefix = '';
private $_die = false;
public function dieOnError($die) { $this->_die = $die; }
private function __construct(Config $config) {
parent::__construct(
$config->host
, $config->username
, $config->password
, $config->database
);
if ($this->connect_error) {
_report_error($this->connect_errno, $this->connect_error);
}
$this->_prefix = $config->prefix;
}
}
Config is a singleton with some public properties.
ErrorHandler
class ErrorHandler extends Object {
/*
* Strip recursion problems in the backtrace
*/
private static function _filter_backtrace($array, $depth = 0) {
$result = array();
foreach ($array as $name => $value) {
switch (gettype($value)) {
case 'object':
case 'unknown type':
case 'resource':
break;
case 'array':
//$result[$name] = self::_filter_backtrace($value);
break;
default:
//$result[$name] = $value;
}
}
return $result;
}
private function _handle_db($errno, $errstr, $errfile, $errline, $backtrace) {
$db = DB::get(Config::get());
$db->dieOnError(true); // prevents infinite loops in error handler
// DB query here
$db->dieOnError(false); // for non-dying.
}
private function __construct() {
}
private static $_instance = null;
public static function get() {
if (self::$_instance === null) {
self::$_instance = new self();
}
return self::$_instance;
}
public function handle($errno, $errstr, $errfile, $errline) {
// No error? Return without reporting
if (!($errno & error_reporting())) {
return;
}
// Type of error
switch ($errno) {
case E_NOTICE:
case E_USER_NOTICE:
$errors = "Notice";
break;
case E_WARNING:
case E_USER_WARNING:
$errors = "Warning";
break;
case E_ERROR:
case E_USER_ERROR:
$errors = "Fatal Error";
break;
default:
$errors = "Unknown";
break;
}
//$backtrace = self::_filter_backtrace(array_shift(debug_backtrace()));
$backtrace = array();
switch (Config::get()->error_log) {
case 'db':
ErrorHandler::_handle_db($errno, $errstr, $errfile, $errline, $backtrace);
break;
default:
// Dump
if (ini_get("display_errors")) {
printf("<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br /><br />\n", $errors, $errstr, $errfile, $errline);
}
// Log
if (ini_get('log_errors')) {
error_log(sprintf("PHP %s: %s in %s on line %d", $errors, $errstr, $errfile, $errline));
}
break;
}
// Exit/return strategy
switch ($errno) {
case E_ERROR:
case E_USER_ERROR:
die();
break;
}
return TRUE;
}
}
Upvotes: 4
Views: 1017
Reputation: 2531
I am not sure if this is related to your problem. I have seen a request sent to the server without a response, meaning no headers or body in the response. If I remember this correctly it was because of the server timing out. Now, I don't remember where this was happen but needed to call my host provider to fix the issue. Also, I think I used output buffering, to send a header so to not fail the response. Try running your script in command line on your server and see if you get the same issue.
Upvotes: 0
Reputation: 197682
You can not log with your error handler into the database when it's not available.
This is the case if the error handler is called prior the database has been initialized.
You need to implement a fallback for this case. Either the error handler can create the database, or you need to create the database first or you log to files in this case, as files are normally available.
In any case to debug your problem consider to enable error logging into an error log (PHP offers this already, just configure it), then you will find out what your specific problem is when you trigger the problem which will give you more information to better solve your issue (e.g. to remove the flaw in dependencies between the logger and the database).
Upvotes: 1