title: Using MySQLi extending class in errorhandler with autoloader
body: <p>I have encountered an issue with using a combination of the following in PHP:</p>
  
  <ul>
  <li>A custom classloader "ClassLoader", implemented as a singleton and registered with spl_autoload_register, which does a require_once to include the classes. Nothing special, just some paths to organize the classes on disk.</li>
  <li>A database class "DB" extending the mysqli class. It's created from a factory which currently knows only one instance. It does little more than load the correct config and offer some shortcut methods.</li>
  <li>A custom errorhandler "ErrorHandler", implemented as a singleton which used the DB class to log notices and warnings to a database.</li>
  </ul>
  
  <p>All of this works fine; the classloader loads classes, the DB class executes queries correctly and the errorhandler logs the errors in the database.</p>
  
  <p>...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.</p>
  
  <p>There are a number of "fixes" I've found:</p>
  
  <ul>
  <li>Do not extend the mysqli class in my "DB" class; this makes it non-functional, but it seems to indicate extending mysqli causes the problem.</li>
  <li>Autoload my own "DB" class either globally or at the top of my ErrorHandler.php class file.</li>
  <li>Explicitely load the "DB" class using require_once just before getting an instance inside the ErrorHandler class.</li>
  </ul>
  
  <p>I can still autoload other classes inside the ErrorHandler, but the moment I instantiate "DB", PHP seems to crash.</p>
  
  <p>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.</p>
  
  <p>Does anybody know what's going on here?</p>
  
  <p>ClassLoader...</p>
  
  <pre><code>class ClassLoader {
      private $_paths = array();
  
      private function __construct() {
          // ... bunch of $this-&gt;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-&gt;_paths)) {
              $this-&gt;_paths[$path] = $format;
              return true;
          }
          return false;
      }
  
      public function autoload($class_name) {
          foreach ($this-&gt;_paths as $path =&gt; $format) {
              $file = $path.'/'.str_replace('$', $class_name, $format);
              if (file_exists($file)) {
                  require_once($file);
                  return true;
              }
          }
          return false;
      }
  }
  
  $autoloader = ClassLoader::get();
  $autoloader-&gt;append(dirname(__FILE__).'/classes');
  spl_autoload_register(array($autoloader, 'autoload'));
  </code></pre>
  
  <p>DB...</p>
  
  <pre><code>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-&gt;_die = $die; }
  
      private function __construct(Config $config) {
          parent::__construct(
                  $config-&gt;host
              ,    $config-&gt;username
              ,    $config-&gt;password
              ,    $config-&gt;database
              );
  
          if ($this-&gt;connect_error) {
              _report_error($this-&gt;connect_errno, $this-&gt;connect_error);
          }
  
          $this-&gt;_prefix = $config-&gt;prefix;
      }
  }
  </code></pre>
  
  <p>Config is a singleton with some public properties.</p>
  
  <p>ErrorHandler</p>
  
  <pre><code>class ErrorHandler extends Object {
      /*
       * Strip recursion problems in the backtrace
       */
      private static function _filter_backtrace($array, $depth = 0) {
          $result = array();
  
          foreach ($array as $name =&gt; $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-&gt;dieOnError(true);    // prevents infinite loops in error handler
          // DB query here
          $db-&gt;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 &amp; 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()-&gt;error_log) {
          case 'db':
              ErrorHandler::_handle_db($errno, $errstr, $errfile, $errline, $backtrace);
              break;
  
          default:
              // Dump
              if (ini_get("display_errors")) {
                  printf("&lt;br /&gt;\n&lt;b&gt;%s&lt;/b&gt;: %s in &lt;b&gt;%s&lt;/b&gt; on line &lt;b&gt;%d&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;\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;
      }
  }
  </code></pre>