Using the extensible MySQLi class in an autoloader error handler

I ran into the problem of using the following combinations in PHP:

  • A custom classloader "ClassLoader" implemented as a singleton and registered with spl_autoload_register, which requires that require_once include classes. Nothing special, just some ways to organize classes on disk.
  • The database class DB extends the mysqli class. It is created from a factory that currently knows only one instance. It does a bit more than loading the correct configuration and offers some quick access methods.
  • The custom error handler "ErrorHandler", implemented as a singleton, which used the DB class to register notifications and warnings in the database.

All of this works great; classloader loads classes, the DB class correctly executes queries, and the error handler logs errors in the database.

... Except when an error occurred before all database operations were called. In this case, PHP crashes; no error messages or anything else, not even any echo or var_dump codes, not even 4xx or 5xx. The browser simply reports that nothing was received.

There are a number of fixes that I found:

  • Do not extend the mysqli class in my "DB" class; this makes it non-functional, but it seems that the mysqli extension is causing the problem.
  • Download your own DB class, either globally or at the top of my ErrorHandler.php file.
  • Explicitly loading the "DB" class with require_once just before getting an instance inside the ErrorHandler class.

I can still autoload other classes inside ErrorHandler, but the moment I create the instance of "DB", PHP seems to crash.

From what I see, the only reasonable explanation is that something in mysqli has problems with being created for the first time inside the ErrorHandler class, since all the fixes that work seem to share the scope aspect, but this doesn't seem to make any sense.

Does anyone know what is 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; } } 

A configuration 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; } } 
+4
source share
2 answers

You cannot register your error handler in the database when it is not available.

This is the case if the error handler is called before the database has been initialized.

You need to implement a backup for this case. Either the error handler can create a database, or first create a database, or start registering files in this case, since files are usually available.

In any case, to debug your problem, consider including error logging in the error log (PHP already offers this, just configure it), then you will find out what your specific problem is when you run the problem, which will give you more information so that it’s better to solve your problem (for example, to eliminate the shortcomings in the dependencies between the registrar and the database).

+1
source

I am not sure if this is related to your problem. I saw a request sent to the server without an answer, which means there are no headers or bodies in the response. If I remember this correctly, it was due to a lack of server. Now I don’t remember where this happened, but I need to call my host provider to fix this problem. In addition, I think I used output buffering to send a header so as not to refuse a response. Try running the script on the command line on your server and see if you have the same problem.

0
source

Source: https://habr.com/ru/post/1380145/


All Articles