diff --git a/CHANGELOG.md b/CHANGELOG.md index c3116cd..86a1cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Cicada changelog ================ +Next release +------------ + +Features: + +* Application accepts an array of configuration values in the constructor +* New `debug` parameter, when true all exception will be catched and a nice formatted page will be shown + 0.4.9 (2014-10-15) ------------------ diff --git a/src/Application.php b/src/Application.php index f0e50f4..67d95f6 100644 --- a/src/Application.php +++ b/src/Application.php @@ -29,10 +29,12 @@ class Application extends \Pimple\Container { use RequestProcessorTrait; - public function __construct() + public function __construct(array $values = array()) { parent::__construct(); + $this['debug'] = false; + $this['router'] = function () { return new Routing\Router(); }; @@ -46,13 +48,17 @@ public function __construct() return new RouteCollection($route); }); - $this['exception_handler'] = function () { - return new ExceptionHandler(); + $this['exception_handler'] = function ($app) { + return new ExceptionHandler($app['debug']); }; $this['emitter'] = function () { return new EventEmitter(); }; + + foreach ($values as $key => $value) { + $this[$key] = $value; + } } public function get($pattern, $callback) diff --git a/src/ExceptionDebugWrapper.php b/src/ExceptionDebugWrapper.php new file mode 100644 index 0000000..9c80215 --- /dev/null +++ b/src/ExceptionDebugWrapper.php @@ -0,0 +1,253 @@ +exception = $exception; + + if (($previous = $exception->getPrevious()) !== null) { + $this->previous = new static($previous); + } + + // TODO Retrieve valuable information that the exception could + // have. For example, if it's an HttpException could be a + // not found exception and the $statusCode should change. + } + + public function getMessage() + { + return $this->exception->getMessage(); + } + + public function getCode() + { + return $this->exception->getCode(); + } + + public function getFile() + { + return $this->exception->getFile(); + } + + public function getLine() + { + return $this->exception->getLine(); + } + + public function getTrace() + { + return $this->exception->getTrace(); + } + + public function getTraceAsString() + { + return $this->exception->getTraceAsString(); + } + + public function getPrevious() + { + return $this->previous; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($statusCode) + { + $this->statusCode = $statusCode; + } + + public function toHTML() + { + return << + + + + + {$this->getMessage()} - Error {$this->getStatusCode()} + + + +

{$this->getMessage()} + + {$this->formatClassName()} in {$this->getFile()} at line {$this->getLine()} + +

+{$this->toHTMLException()} + + + +EOF; + } + + public function toHTMLException() + { + $list = ''; + + foreach ($this->exception->getTrace() as $trace) { + $class = ''; + $line = ''; + $args = ''; + + if (isset($trace['class']) && isset($trace['type'])) { + $class = $this->formatClassName($trace['class']) . $trace['type']; + } + + if (isset($trace['file']) && isset($trace['line'])) { + $line = "in {$trace['file']} at line {$trace['line']}"; + } + + if (isset($trace['args'])) { + $args = implode(', ', $this->formatArguments($trace['args'])); + } + + $list .= << +
{$class}{$trace['function']}($args)
+
$line
+ +EOF; + } + + $previous = ''; + if ($this->getPrevious() !== null) { + $previous = $this->getPrevious()->toHTMLException(); + } + + return << +

{$this->formatClassName()}: {$this->getMessage()}

+
    {$list} +
+ +{$previous} +EOF; + } + + protected function formatClassName($class = null) + { + if ($class === null) { + $class = get_class($this->exception); + } + + $className = ltrim(strrchr($class, '\\'), '\\'); + + if (empty($className)) { + return $class; + } + + return "$className"; + } + + protected function formatArguments(array $arguments) + { + $args = []; + + foreach ($arguments as $key => $argument) { + if (is_object($argument)) { + if (!method_exists($argument, '__debugInfo')) { + $args[] = $this->formatClassName( + get_class($argument) + ); + continue; + } + $argument = $arg->__debugInfo(); + } + + switch (gettype($argument)) { + case 'integer': + case 'double': + $args[] = $argument; + break; + + case 'string': + $args[] = '"' . $argument . '"'; + break; + + case 'boolean': + $args[] = $argument ? 'true' : 'false'; + break; + + case 'array': + if (empty($argument)) { + $args[] = '[]'; + break; + } + + $keys = array_keys($argument); + $array = $this->formatArguments($argument); + + if (!is_int(current($keys))) { + $array = array_map(function($key, $value) { + return "\"$key\"=>$value"; + }, $keys, $array); + + $args[] = '{' . implode(', ', $array) . '}'; + break; + } + + $args[] = '[' . implode(', ', $array) . ']'; + break; + + default: + $args[] = gettype($argument); + } + } + + return $args; + } +} \ No newline at end of file diff --git a/src/ExceptionHandler.php b/src/ExceptionHandler.php index 7e30c41..00a81f6 100644 --- a/src/ExceptionHandler.php +++ b/src/ExceptionHandler.php @@ -16,10 +16,18 @@ */ namespace Cicada; +use Symfony\Component\HttpFoundation\Response; + class ExceptionHandler { + private $debug = false; private $callbacks = []; + public function __construct($debug = false) + { + $this->debug = $debug; + } + /** * Adds an exception callback. * @@ -75,6 +83,10 @@ public function add(callable $callback) */ public function handle(\Exception $ex) { + if ($this->debug) { + return $this->handleDebug($ex); + } + foreach ($this->callbacks as $exClass => $callback) { if ($ex instanceof $exClass) { return $callback($ex); @@ -82,6 +94,13 @@ public function handle(\Exception $ex) } } + public function handleDebug(\Exception $ex) + { + $wrapped = new ExceptionDebugWrapper($ex); + + return new Response($wrapped->toHTML(), $wrapped->getStatusCode()); + } + /** Returns the handlers array */ public function getCallbacks() { diff --git a/tests/ExceptionHandlerTest.php b/tests/ExceptionHandlerTest.php index 9c9af0f..d663211 100644 --- a/tests/ExceptionHandlerTest.php +++ b/tests/ExceptionHandlerTest.php @@ -87,4 +87,17 @@ public function testNoMaches() $actual = $handler->handle(new \BadMethodCallException()); $this->assertNull($actual); } + + public function testDebug() + { + $handler = new ExceptionHandler(true); + $handler->add(function(\Exception $ex) { return 1; }); + + $actual = $handler->handle(new \Exception('Help', 123)); + + $this->assertNotEquals($actual, 1); + $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $actual); + $this->assertRegExp('/Help/', $actual->getContent()); + $this->assertEquals(500, $actual->getStatusCode()); + } }