Usando MySQLi extendiendo la clase en errorhandler con autocargador

I have encountered an issue with using a combination of the following in PHP:

  • 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.
  • 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.
  • A custom errorhandler "ErrorHandler", implemented as a singleton which used the DB class to log notices and warnings to a database.

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:

  • 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.
  • Autoload my own "DB" class either globally or at the top of my ErrorHandler.php class file.
  • Explicitely load the "DB" class using require_once just before getting an instance inside the ErrorHandler class.

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.

¿Alguien sabe lo que está pasando aquí?

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;
    }
}

preguntado el 08 de noviembre de 11 a las 09:11

Do you see anything in the webserver error logs? -

@lanzz I couldn't find anything related to this in the webserver logs. -

2 Respuestas

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 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).

respondido 13 nov., 11:15

DB::get() is supposed to create/return a single instance of the DB and does so everywhere except when used inside the error handler. Do you mean to say that you cannot open a DB from within an error handler? I'll include basic error logging before any DB activity in the error handler and see what I get. - MartijnCMT

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.

Respondido el 13 de diciembre de 11 a las 21:12

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.