dprfe04886 2018-12-14 07:43
浏览 93
已采纳

异常时捕获请求状态

I have a Slim Framework application with a custom errorHandler, and a small middleware stack. My middleware adds attributes to the Request object which I would like to have access to from my error handler in the event of an exception. For example:

$app->get('/endpoint', function($request $response, $args) {
    $myAttribute = $request->getAttribute('myAttribute'); //returns 'myValue'
    throw new \Exception(); //any code that throws an error
})->add(function($request, $response, $next) {
    $request = $request->withAttribute('myAttribute', 'myValue');
    $response = $next($request, $response);
    return $response;
});

$app->getContainer['errorHandler'] = function($c) {
    return function($request, $response, $exception) {
        $myAttribute = $request->getAttribute('myAttribute'); //returns null
        return $response;
    }
};

The attribute does not exist within the Request object inside the error handler because the cloned Request from inside the route has not been returned after traversing through the middleware stack. Is it possible to access the Request and Response objects as they exist, at the time (in the location) the exception is thrown? I can't explicitly pass them (for example, SlimException) because I'm trying to handle unexpected errors as well.

  • 写回答

1条回答 默认 最新

  • douxin2011 2019-04-21 17:34
    关注

    I've created two, somewhat hacky, solutions to capturing Request and Response state when exceptions are thrown. Both attempt to insert a try/catch as close to the center of the middleware stack as possible (without repeating code), and involve wrapping the original exception in a new exception class together with the modified endpoint parameters.

    Middleware

    This works as long as attention is paid to the order that middleware is added. Unfortunately it does require the innermost middleware is added to each route, or at the very least, "before" the middleware modifying the Request and/or Response objects. This won't catch any changes to Request/Response made inside, nor after the route.

    class WrappedException extends \Exception {
        public $exception;
        public $request;
        public $response;
    
        public function __construct($exception, $request, $response) {
            $this->exception = $exception;
            $this->request = $request;
            $this->response = $response;
        }
    }
    
    $app->get('/endpoint', function($request $response, $args) {
        throw new \Exception(); //any code that throws an error
    })->add(function($request, $response, $next) {
        try {
            $response = $next($request, $response);
        } catch (\Exception $exception) {
            throw new WrappedException($exception, $request, $response);
        }
        return $response;
    });
    
    $app->add(function($request, $response, $next) {
        $request = $request->withAttribute('myAttribute', 'myValue');
        $response = $next($request, $response);
        return $response;
    });
    
    $app->getContainer['errorHandler'] = function($c) {
        return function($request, $response, $exception) {
            if ($exception instanceof WrappedException) {
                //returns 'myValue'
                $myAttribute = $exception->request->getAttribute('myAttribute');
            }
            return $response;
        }
    };
    

    Route Class

    This wraps all routes in a try block, and makes for a bit cleaner route code, but you must be sure to extend all routes from the RouteBase class.

    class WrappedException extends \Exception {
        public $exception;
        public $request;
        public $response;
    
        public function __construct($exception, $request = null, $response = null) {
            $this->exception = $exception;
            $this->request = $request;
            $this->response = $response;
        }
    }
    
    class RouteBase {
        public function __call($method, $arguments) {
            if (method_exists($this, $method)) {
                try {
                    $this::$method(...$arguments);
                } catch (\Exception $e) {
                    throw new WrappedException($e, ...$arguments);
                }
            } else {
                throw new \Exception('Route method not found.');
            }
        }
    }
    
    class RouteClass extends RouteBase {
        //PROTECTED -- must be callable by parent class, but not outside class
        protected function get(Request $request, Response $response, $args) {
            throw new \Exception('A new exception!');
            $response->write('hey');
            return $response;
        }
    }
    
    $app->get('/endpoint', 'RouteClass:get');
    
    $app->add(function($request, $response, $next) {
        $request = $request->withAttribute('myAttribute', 'myValue');
        $response = $next($request, $response);
        return $response;
    });
    
    $app->getContainer['errorHandler'] = function($c) {
        return function($request, $response, $exception) {
            if ($exception instanceof WrappedException) {
                //returns 'myValue'
                $myAttribute = $exception->request->getAttribute('myAttribute');
            }
            return $response;
        }
    };
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 vc6.0中想运行代码的时候总是提示无法打开文件是怎么回事
  • ¥25 关于##爬虫##的问题,如何解决?:
  • ¥15 ZABBIX6.0L连接数据库报错,如何解决?(操作系统-centos)
  • ¥15 找一位技术过硬的游戏pj程序员
  • ¥15 matlab生成电测深三层曲线模型代码
  • ¥50 随机森林与房贷信用风险模型
  • ¥50 buildozer打包kivy app失败
  • ¥30 在vs2022里运行python代码
  • ¥15 不同尺寸货物如何寻找合适的包装箱型谱
  • ¥15 求解 yolo算法问题