Overview

Namespaces

  • Charcoal
    • App
      • Action
      • Config
      • Handler
      • Middleware
      • Module
      • Route
      • Script
      • ServiceProvider
      • Template

Classes

  • Charcoal\App\Action\AbstractAction
  • Charcoal\App\App
  • Charcoal\App\AppConfig
  • Charcoal\App\AppContainer
  • Charcoal\App\Config\CacheConfig
  • Charcoal\App\Config\DatabaseConfig
  • Charcoal\App\Config\FilesystemConfig
  • Charcoal\App\Config\LoggerConfig
  • Charcoal\App\Config\MemcacheCacheConfig
  • Charcoal\App\Config\MemcacheCacheServerConfig
  • Charcoal\App\Handler\AbstractHandler
  • Charcoal\App\Handler\Error
  • Charcoal\App\Handler\HandlerConfig
  • Charcoal\App\Handler\NotAllowed
  • Charcoal\App\Handler\NotFound
  • Charcoal\App\Handler\PhpError
  • Charcoal\App\Handler\Shutdown
  • Charcoal\App\Middleware\CacheMiddleware
  • Charcoal\App\Module\AbstractModule
  • Charcoal\App\Module\ModuleConfig
  • Charcoal\App\Module\ModuleManager
  • Charcoal\App\Route\ActionRoute
  • Charcoal\App\Route\ActionRouteConfig
  • Charcoal\App\Route\RouteConfig
  • Charcoal\App\Route\RouteManager
  • Charcoal\App\Route\ScriptRoute
  • Charcoal\App\Route\ScriptRouteConfig
  • Charcoal\App\Route\TemplateRoute
  • Charcoal\App\Route\TemplateRouteConfig
  • Charcoal\App\Script\AbstractScript
  • Charcoal\App\ServiceProvider\AppServiceProvider
  • Charcoal\App\ServiceProvider\CacheServiceProvider
  • Charcoal\App\ServiceProvider\DatabaseServiceProvider
  • Charcoal\App\ServiceProvider\FilesystemServiceProvider
  • Charcoal\App\ServiceProvider\LoggerServiceProvider
  • Charcoal\App\ServiceProvider\ViewServiceProvider
  • Charcoal\App\Template\AbstractTemplate
  • Charcoal\App\Template\AbstractWidget
  • Charcoal\App\Template\WidgetBuilder

Interfaces

  • Charcoal\App\Action\ActionInterface
  • Charcoal\App\AppAwareInterface
  • Charcoal\App\Handler\HandlerInterface
  • Charcoal\App\Module\ModuleInterface
  • Charcoal\App\Route\RouteInterface
  • Charcoal\App\Script\CronScriptInterface
  • Charcoal\App\Script\ScriptInterface
  • Charcoal\App\Template\TemplateInterface
  • Charcoal\App\Template\WidgetInterface

Traits

  • Charcoal\App\AppAwareTrait
  • Charcoal\App\CallableResolverAwareTrait
  • Charcoal\App\Script\ArgScriptTrait
  • Charcoal\App\Script\CronScriptTrait
  • Charcoal\App\Script\PathScriptTrait
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Charcoal\App\Handler;
  4: 
  5: use Exception;
  6: 
  7: // Dependencies from PSR-7 (HTTP Messaging)
  8: use Psr\Http\Message\ServerRequestInterface;
  9: use Psr\Http\Message\ResponseInterface;
 10: 
 11: // Dependency from Slim
 12: use Slim\Http\Body;
 13: 
 14: // Dependency from Pimple
 15: use Pimple\Container;
 16: 
 17: // Local Dependencies
 18: use Charcoal\App\Handler\AbstractHandler;
 19: 
 20: /**
 21:  * Error Handler
 22:  *
 23:  * Enhanced version of {@see \Slim\Handlers\NotFound}.
 24:  *
 25:  * It outputs the error message and diagnostic information in either
 26:  * JSON, XML, or HTML based on the Accept header.
 27:  */
 28: class Error extends AbstractHandler
 29: {
 30:     /**
 31:      * Whether to output the error's details.
 32:      *
 33:      * @var boolean $displayErrorDetails
 34:      */
 35:     protected $displayErrorDetails;
 36: 
 37:     /**
 38:      * The caught exception.
 39:      *
 40:      * @var Exception $exception
 41:      */
 42:     protected $exception;
 43: 
 44:     /**
 45:      * Inject dependencies from a Pimple Container.
 46:      *
 47:      * ## Dependencies
 48:      *
 49:      * - `array $settings` — Slim's settings.
 50:      *
 51:      * @param  Container $container A dependencies container instance.
 52:      * @return Error Chainable
 53:      */
 54:     public function setDependencies(Container $container)
 55:     {
 56:         parent::setDependencies($container);
 57: 
 58:         $displayDetails = $container['settings']['displayErrorDetails'];
 59:         $this->setDisplayErrorDetails($displayDetails);
 60: 
 61:         return $this;
 62:     }
 63: 
 64:     /**
 65:      * Invoke Error Handler
 66:      *
 67:      * @param  ServerRequestInterface $request  The most recent Request object.
 68:      * @param  ResponseInterface      $response The most recent Response object.
 69:      * @param  Exception              $error    The caught Exception object.
 70:      * @return ResponseInterface
 71:      */
 72:     public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Exception $error)
 73:     {
 74:         $this->setException($error);
 75:         $this->logger->error($error->getMessage());
 76:         $this->logger->error($error->getFile().':'.$error->getLine());
 77: 
 78:         $contentType = $this->determineContentType($request);
 79:         switch ($contentType) {
 80:             case 'application/json':
 81:                 $output = $this->renderJsonOutput();
 82:                 break;
 83: 
 84:             case 'text/xml':
 85:             case 'application/xml':
 86:                 $output = $this->renderXmlOutput();
 87:                 break;
 88: 
 89:             case 'text/html':
 90:             default:
 91:                 $output = $this->renderHtmlOutput();
 92:                 break;
 93:         }
 94: 
 95:         $this->writeToErrorLog();
 96: 
 97:         $body = new Body(fopen('php://temp', 'r+'));
 98:         $body->write($output);
 99: 
100:         return $response
101:                 ->withStatus(500)
102:                 ->withHeader('Content-type', $contentType)
103:                 ->withBody($body);
104:     }
105: 
106:     /**
107:      * Set whether to display details of the error or a generic message.
108:      *
109:      * @param  boolean $state Whether to display error details.
110:      * @return Error Chainable
111:      */
112:     protected function setDisplayErrorDetails($state)
113:     {
114:         $this->displayErrorDetails = (boolean)$state;
115: 
116:         return $this;
117:     }
118: 
119:     /**
120:      * Retrieves the HTTP methods allowed by the current request.
121:      *
122:      * @return boolean
123:      */
124:     public function displayErrorDetails()
125:     {
126:         return $this->displayErrorDetails;
127:     }
128: 
129:     /**
130:      * Set the caught error.
131:      *
132:      * @param  Exception $error The caught Exception object.
133:      * @return Error Chainable
134:      */
135:     protected function setException(Exception $error)
136:     {
137:         $this->exception = $error;
138: 
139:         return $this;
140:     }
141: 
142:     /**
143:      * Retrieves the caught error.
144:      *
145:      * @return Exception
146:      */
147:     public function exception()
148:     {
149:         return $this->exception;
150:     }
151: 
152:     /**
153:      * Write to the error log if displayErrorDetails is false
154:      *
155:      * @return void
156:      */
157:     protected function writeToErrorLog()
158:     {
159:         if ($this->displayErrorDetails()) {
160:             return;
161:         }
162: 
163:         $error = $this->exception();
164: 
165:         $message  = $this->translator()->translate('Application Error').':'.PHP_EOL;
166:         $message .= $this->renderTextError($error);
167:         while ($error = $error->getPrevious()) {
168:             $message .= PHP_EOL.'Previous exception:'.PHP_EOL;
169:             $message .= $this->renderTextError($error);
170:         }
171: 
172:         $message .= PHP_EOL.'View in rendered output by enabling the "displayErrorDetails" setting.'.PHP_EOL;
173: 
174:         error_log($message);
175:     }
176: 
177:     /**
178:      * Render error as Text.
179:      *
180:      * @param  Exception $error The caught Exception object.
181:      * @return string
182:      */
183:     protected function renderTextError(Exception $error)
184:     {
185:         $code    = $error->getCode();
186:         $message = $error->getMessage();
187:         $file    = $error->getFile();
188:         $line    = $error->getLine();
189:         $trace   = $error->getTraceAsString();
190: 
191:         $text = sprintf('Type: %s'.PHP_EOL, get_class($error));
192: 
193:         if ($code) {
194:             $text .= sprintf('Code: %s'.PHP_EOL, $code);
195:         }
196: 
197:         if ($message) {
198:             $text .= sprintf('Message: %s'.PHP_EOL, htmlentities($message));
199:         }
200: 
201:         if ($file) {
202:             $text .= sprintf('File: %s'.PHP_EOL, $file);
203:         }
204: 
205:         if ($line) {
206:             $text .= sprintf('Line: %s'.PHP_EOL, $line);
207:         }
208: 
209:         if ($trace) {
210:             $text .= sprintf('Trace: %s', $trace);
211:         }
212: 
213:         return $text;
214:     }
215: 
216:     /**
217:      * Render JSON Error
218:      *
219:      * @return string
220:      */
221:     protected function renderJsonOutput()
222:     {
223:         $error = $this->exception();
224:         $json  = [
225:             'message' => $this->translator()->translate('Application Error'),
226:         ];
227: 
228:         if ($this->displayErrorDetails()) {
229:             $json['error'] = [];
230: 
231:             do {
232:                 $json['error'][] = [
233:                     'type'    => get_class($error),
234:                     'code'    => $error->getCode(),
235:                     'message' => $error->getMessage(),
236:                     'file'    => $error->getFile(),
237:                     'line'    => $error->getLine(),
238:                     'trace'   => explode("\n", $error->getTraceAsString()),
239:                 ];
240:             } while ($error = $error->getPrevious());
241:         }
242: 
243:         return json_encode($json, JSON_PRETTY_PRINT);
244:     }
245: 
246:     /**
247:      * Render XML Error
248:      *
249:      * @return string
250:      */
251:     protected function renderXmlOutput()
252:     {
253:         $error = $this->exception();
254:         $title = $this->messageTitle();
255: 
256:         $xml = "<error>\n  <message>".$title."</message>\n";
257:         if ($this->displayErrorDetails()) {
258:             do {
259:                 $xml .= "  <exception>\n";
260:                 $xml .= '    <type>'.get_class($error)."</type>\n";
261:                 $xml .= '    <code>'.$error->getCode()."</code>\n";
262:                 $xml .= '    <message>'.$this->createCdataSection($error->getMessage())."</message>\n";
263:                 $xml .= '    <file>'.$error->getFile()."</file>\n";
264:                 $xml .= '    <line>'.$error->getLine()."</line>\n";
265:                 $xml .= '    <trace>'.$this->createCdataSection($error->getTraceAsString())."</trace>\n";
266:                 $xml .= "  </exception>\n";
267:             } while ($error = $error->getPrevious());
268:         }
269:         $xml .= '</error>';
270: 
271:         return $xml;
272:     }
273: 
274:     /**
275:      * Returns a CDATA section with the given content.
276:      *
277:      * @param  string $content The Character-Data to mark.
278:      * @return string
279:      */
280:     private function createCdataSection($content)
281:     {
282:         return sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $content));
283:     }
284: 
285:     /**
286:      * Render error as HTML.
287:      *
288:      * @param  Exception $error The caught Exception object.
289:      * @return string
290:      */
291:     protected function renderHtmlError(Exception $error)
292:     {
293:         $code    = $error->getCode();
294:         $message = $error->getMessage();
295:         $file    = $error->getFile();
296:         $line    = $error->getLine();
297:         $trace   = $error->getTraceAsString();
298: 
299:         $html = sprintf('<div><strong>Type:</strong> %s</div>', get_class($error));
300: 
301:         if ($code) {
302:             $html .= sprintf('<div><strong>Code:</strong> %s</div>', $code);
303:         }
304: 
305:         if ($message) {
306:             $html .= sprintf('<div><strong>Message:</strong> %s</div>', htmlentities($message));
307:         }
308: 
309:         if ($file) {
310:             $html .= sprintf('<div><strong>File:</strong> %s</div>', $file);
311:         }
312: 
313:         if ($line) {
314:             $html .= sprintf('<div><strong>Line:</strong> %s</div>', $line);
315:         }
316: 
317:         if ($trace) {
318:             $html .= '<h2>Trace</h2>';
319:             $html .= sprintf('<pre>%s</pre>', htmlentities($trace));
320:         }
321: 
322:         return $html;
323:     }
324: 
325:     /**
326:      * Render body of HTML error
327:      *
328:      * @return string
329:      */
330:     public function renderHtmlMessage()
331:     {
332:         $error = $this->exception();
333: 
334:         if ($this->displayErrorDetails()) {
335:             $html  = '<p>The application could not run because of the following error:</p>';
336:             $html .= '<h2>Details</h2>';
337:             $html .= $this->renderHtmlError($error);
338: 
339:             while ($error = $error->getPrevious()) {
340:                 $html .= '<h2>Previous Exception</h2>';
341:                 $html .= $this->renderHtmlError($error);
342:             }
343:         } else {
344:             $html = '<p>'.$this->translator()->translate('A website error has occurred. Sorry for the temporary inconvenience.').'</p>';
345:         }
346: 
347:         $title   = $this->messageTitle();
348:         $message = '<h1>'.$title."</h1>\n".$html."\n";
349: 
350:         return $message;
351:     }
352: }
353: 
API documentation generated by ApiGen