Browse Source

Implementing a backwards compatible way of handling PHP 7 Errors

Jose Lorenzo Rodriguez 10 years ago
parent
commit
8410fd4b34
2 changed files with 51 additions and 8 deletions
  1. 27 2
      src/Error/BaseErrorHandler.php
  2. 24 6
      src/Error/ExceptionRenderer.php

+ 27 - 2
src/Error/BaseErrorHandler.php

@@ -18,6 +18,8 @@ use Cake\Core\Configure;
 use Cake\Log\Log;
 use Cake\Routing\Router;
 use Exception;
+use Error;
+use Cake\Error\PHP7ErrorException;
 
 /**
  * Base error handler that provides logic common to the CLI + web
@@ -65,7 +67,7 @@ abstract class BaseErrorHandler
         }
         error_reporting($level);
         set_error_handler([$this, 'handleError'], $level);
-        set_exception_handler([$this, 'handleException']);
+        set_exception_handler([$this, 'wrapAndHandleException']);
         register_shutdown_function(function () {
             if (PHP_SAPI === 'cli') {
                 return;
@@ -140,6 +142,22 @@ abstract class BaseErrorHandler
     }
 
     /**
+     * Checks the passed exception type. If it is an instance of `Error`
+     * then, it wraps the passed object inside another Exception object
+     * for backwards compatibility purposes.
+     *
+     * @param Exception|Error $exception The exception to handle
+     * @return void
+     */
+    public function wrapAndHandleException($exception)
+    {
+        if ($exception instanceof Error) {
+            $exception = new PHP7ErrorException($exception);
+        }
+        $this->handleException($exception);
+    }
+
+    /**
      * Handle uncaught exceptions.
      *
      * Uses a template method provided by subclasses to display errors in an
@@ -231,13 +249,17 @@ abstract class BaseErrorHandler
     protected function _logException(Exception $exception)
     {
         $config = $this->_options;
+        $unwrapped = $exception instanceof PHP7ErrorException ?
+            $exception->getError() :
+            $exception;
+
         if (empty($config['log'])) {
             return false;
         }
 
         if (!empty($config['skipLog'])) {
             foreach ((array)$config['skipLog'] as $class) {
-                if ($exception instanceof $class) {
+                if ($unwrapped instanceof $class) {
                     return false;
                 }
             }
@@ -253,6 +275,9 @@ abstract class BaseErrorHandler
      */
     protected function _getMessage(Exception $exception)
     {
+        $exception = $exception instanceof PHP7ErrorException ?
+            $exception->getError() :
+            $exception;
         $config = $this->_options;
         $message = sprintf(
             "[%s] %s",

+ 24 - 6
src/Error/ExceptionRenderer.php

@@ -19,6 +19,7 @@ use Cake\Core\App;
 use Cake\Core\Configure;
 use Cake\Core\Exception\Exception as CakeException;
 use Cake\Core\Exception\MissingPluginException;
+use Cake\Error\PHP7ErrorException;
 use Cake\Event\Event;
 use Cake\Network\Exception\HttpException;
 use Cake\Network\Request;
@@ -92,6 +93,18 @@ class ExceptionRenderer
     }
 
     /**
+     * Returns the unwrapped exception object in case we are dealing with
+     * a PHP 7 Error object
+     *
+     * @param \Exception $exception The object to unwrap
+     * @return \Exception|\Error
+     */
+    protected function _unwrap($exception)
+    {
+        return $exception instanceof PHP7ErrorException ? $exception->getError() : $exception;
+    }
+
+    /**
      * Get the controller instance to handle the exception.
      * Override this method in subclasses to customize the controller used.
      * This method returns the built in `ErrorController` normally, or if an error is repeated
@@ -143,12 +156,13 @@ class ExceptionRenderer
         $code = $this->_code($exception);
         $method = $this->_method($exception);
         $template = $this->_template($exception, $method, $code);
+        $unwrapped = $this->_unwrap($exception);
 
         $isDebug = Configure::read('debug');
         if (($isDebug || $exception instanceof HttpException) &&
             method_exists($this, $method)
         ) {
-            return $this->_customMethod($method, $exception);
+            return $this->_customMethod($method, $unwrapped);
         }
 
         $message = $this->_message($exception, $code);
@@ -161,12 +175,12 @@ class ExceptionRenderer
         $viewVars = [
             'message' => $message,
             'url' => h($url),
-            'error' => $exception,
+            'error' => $unwrapped,
             'code' => $code,
             '_serialize' => ['message', 'url', 'code']
         ];
         if ($isDebug) {
-            $viewVars['trace'] = Debugger::formatTrace($exception->getTrace(), [
+            $viewVars['trace'] = Debugger::formatTrace($unwrapped->getTrace(), [
                 'format' => 'array',
                 'args' => false
             ]);
@@ -174,8 +188,8 @@ class ExceptionRenderer
         }
         $this->controller->set($viewVars);
 
-        if ($exception instanceof CakeException && $isDebug) {
-            $this->controller->set($this->error->getAttributes());
+        if ($unwrapped instanceof CakeException && $isDebug) {
+            $this->controller->set($unwrapped->getAttributes());
         }
         return $this->_outputMessage($template);
     }
@@ -205,6 +219,7 @@ class ExceptionRenderer
      */
     protected function _method(Exception $exception)
     {
+        $exception = $this->_unwrap($exception);
         list(, $baseClass) = namespaceSplit(get_class($exception));
 
         if (substr($baseClass, -9) === 'Exception') {
@@ -224,7 +239,8 @@ class ExceptionRenderer
      */
     protected function _message(Exception $exception, $code)
     {
-        $message = $this->error->getMessage();
+        $exception = $this->_unwrap($exception);
+        $message = $exception->getMessage();
 
         if (!Configure::read('debug') &&
             !($exception instanceof HttpException)
@@ -249,6 +265,7 @@ class ExceptionRenderer
      */
     protected function _template(Exception $exception, $method, $code)
     {
+        $exception = $this->_unwrap($exception);
         $isHttpException = $exception instanceof HttpException;
 
         if (!Configure::read('debug') && !$isHttpException) {
@@ -285,6 +302,7 @@ class ExceptionRenderer
     protected function _code(Exception $exception)
     {
         $code = 500;
+        $exception = $this->_unwrap($exception);
         $errorCode = $exception->getCode();
         if ($errorCode >= 400 && $errorCode < 506) {
             $code = $errorCode;