44
55use Codemonster \Errors \Contracts \ExceptionHandlerInterface ;
66use Codemonster \Http \Response ;
7+ use Closure ;
78use Throwable ;
89
910class SmartExceptionHandler implements ExceptionHandlerInterface
1011{
11- protected $ viewRenderer ;
12+ protected ? Closure $ viewRenderer ;
1213 protected bool $ debug ;
14+ protected string $ templatePath ;
1315
14- public function __construct (?callable $ viewRenderer = null , bool $ debug = false )
16+ /**
17+ * Order: errors.debug (debug=true), errors.{status}, errors.generic, fallback plain-text.
18+ */
19+ public function __construct (?callable $ viewRenderer = null , bool $ debug = false , ?string $ templatePath = null )
1520 {
16- $ this ->viewRenderer = $ viewRenderer ;
21+ $ this ->viewRenderer = $ viewRenderer ? Closure:: fromCallable ( $ viewRenderer ) : null ;
1722 $ this ->debug = $ debug ;
23+ $ this ->templatePath = $ templatePath ?? (dirname (__DIR__ , 2 ) . '/resources/views/errors ' );
1824 }
1925
2026 public function handle (Throwable $ e ): Response
@@ -26,8 +32,12 @@ public function handle(Throwable $e): Response
2632 $ status = $ e ->getStatusCode ();
2733 }
2834
35+ if (!is_int ($ status ) || $ status < 100 || $ status > 599 ) {
36+ $ status = 500 ;
37+ }
38+
2939 if ($ this ->debug ) {
30- return $ this ->renderTemplate ('errors.debug ' , ['exception ' => $ e ], $ status ) ?? $ this ->fallbackDebug ($ e );
40+ return $ this ->renderTemplate ('errors.debug ' , ['exception ' => $ e ], $ status ) ?? $ this ->fallbackDebug ($ e, $ status );
3141 }
3242
3343 return $ this ->renderTemplate (
@@ -55,21 +65,28 @@ protected function renderTemplate(string $template, array $data, int $status): ?
5565 {
5666 if ($ this ->viewRenderer ) {
5767 try {
58- $ html = call_user_func ($ this ->viewRenderer , $ template , $ data );
68+ $ html = ($ this ->viewRenderer )( $ template , $ data );
5969
6070 if ($ html ) {
6171 return new Response ($ html , $ status , ['Content-Type ' => 'text/html ' ]);
6272 }
63- } catch (Throwable ) {
73+ } catch (Throwable $ renderError ) {
74+ if ($ this ->debug ) {
75+ throw $ renderError ;
76+ }
6477 }
6578 }
6679
67- $ basePath = dirname ( __DIR__ , 2 ) . ' /resources/views/errors ' ;
80+ $ basePath = $ this -> templatePath ;
6881 $ fileMap = [
6982 'errors.generic ' => "$ basePath/generic.php " ,
7083 'errors.debug ' => "$ basePath/debug.php " ,
7184 ];
7285
86+ if (!isset ($ fileMap [$ template ]) && preg_match ('/^errors\.(\d{3})$/ ' , $ template , $ matches )) {
87+ $ fileMap [$ template ] = sprintf ('%s/%s.php ' , $ basePath , $ matches [1 ]);
88+ }
89+
7390 if (isset ($ fileMap [$ template ]) && is_file ($ fileMap [$ template ])) {
7491 ob_start ();
7592 extract ($ data , EXTR_SKIP );
@@ -87,17 +104,14 @@ protected function renderTemplate(string $template, array $data, int $status): ?
87104 protected function fallbackPlain (Throwable $ e , int $ status ): Response
88105 {
89106 $ content = sprintf (
90- "HTTP %d %s \nin %s:%d " ,
91- $ status ,
92- $ e ->getMessage (),
93- $ e ->getFile (),
94- $ e ->getLine ()
107+ "HTTP %d \nAn unexpected error occurred. " ,
108+ $ status
95109 );
96110
97111 return new Response ($ content , $ status , ['Content-Type ' => 'text/plain ' ]);
98112 }
99113
100- protected function fallbackDebug (Throwable $ e ): Response
114+ protected function fallbackDebug (Throwable $ e, int $ status ): Response
101115 {
102116 $ content = sprintf (
103117 "[%s] %s \nin %s:%d \n\n%s " ,
@@ -108,6 +122,6 @@ protected function fallbackDebug(Throwable $e): Response
108122 $ e ->getTraceAsString ()
109123 );
110124
111- return new Response ($ content , 500 , ['Content-Type ' => 'text/plain ' ]);
125+ return new Response ($ content , $ status , ['Content-Type ' => 'text/plain ' ]);
112126 }
113127}
0 commit comments