Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.

Adds ability to specify a template for exceptions retrieved from Exception::getPrevious #4542

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 59 additions & 2 deletions library/Zend/Mvc/View/Console/ExceptionStrategy.php
Expand Up @@ -39,9 +39,22 @@ class ExceptionStrategy extends AbstractListenerAggregate
:stack
======================================================================
Previous Exception(s):
======================================================================
:previous

EOT;

/**
* A template for message to show in console when an exception has previous exceptions.
* @var string
*/
protected $previousMessage = <<<EOT
======================================================================
:className
:message
----------------------------------------------------------------------
:file::line
:stack

EOT;

/**
Expand Down Expand Up @@ -111,6 +124,26 @@ public function setMessage($message)
return $this;
}

/**
* Sets template for previous message that will be shown in Console.
*
* @param string $previousMessage
* @return ExceptionStrategy
*/
public function setPreviousMessage($previousMessage)
{
$this->previousMessage = $previousMessage;
return $this;
}

/**
* @return callable|string
*/
public function getPreviousMessage()
{
return $this->previousMessage;
}

/**
* Create an exception view model, and set the HTTP status code
*
Expand Down Expand Up @@ -152,6 +185,30 @@ public function prepareExceptionViewModel(MvcEvent $e)
$callback = $this->message;
$message = (string) $callback($exception, $this->displayExceptions);
} elseif ($this->displayExceptions && $exception instanceof \Exception) {
$previous = '';
$previousException = $exception->getPrevious();
while($previousException) {
$previous .= str_replace(
array(
':className',
':message',
':code',
':file',
':line',
':stack',
),array(
get_class($previousException),
$previousException->getMessage(),
$previousException->getCode(),
$previousException->getFile(),
$previousException->getLine(),
$exception->getTraceAsString(),
),
$this->previousMessage
);
$previousException = $previousException->getPrevious();
}

/* @var $exception \Exception */
$message = str_replace(
array(
Expand All @@ -169,7 +226,7 @@ public function prepareExceptionViewModel(MvcEvent $e)
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString(),
$exception->getPrevious(),
$previous
),
$this->message
);
Expand Down
226 changes: 226 additions & 0 deletions tests/ZendTest/Mvc/View/Console/ExceptionStrategyTest.php
@@ -0,0 +1,226 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @package Zend_Mvc
*/

namespace ZendTest\Mvc\View\Console;

use PHPUnit_Framework_TestCase as TestCase;
use Zend\Console\Response;
use Zend\EventManager\EventManager;
use Zend\Mvc\Application;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\View\Console\ExceptionStrategy;
use Zend\View\Model;
use Zend\View\Model\ConsoleModel;

/**
* @category Zend
* @package Zend_Mvc
* @subpackage UnitTest
*/
class ExceptionStrategyTest extends TestCase
{
protected $strategy;

public function setUp()
{
$this->strategy = new ExceptionStrategy();
}

public function testEventListeners()
{
$events = new EventManager();
$events->attachAggregate($this->strategy);

$listeners = $events->getListeners(MvcEvent::EVENT_DISPATCH_ERROR);
$expectedCallback = array($this->strategy, 'prepareExceptionViewModel');
$expectedPriority = 1;
$found = false;
foreach ($listeners as $listener) {
$callback = $listener->getCallback();
if ($callback === $expectedCallback) {
if ($listener->getMetadatum('priority') == $expectedPriority) {
$found = true;
break;
}
}
}
$this->assertTrue($found, 'MvcEvent::EVENT_DISPATCH_ERROR not found');


$listeners = $events->getListeners(MvcEvent::EVENT_RENDER_ERROR);
$expectedCallback = array($this->strategy, 'prepareExceptionViewModel');
$expectedPriority = 1;
$found = false;
foreach ($listeners as $listener) {
$callback = $listener->getCallback();
if ($callback === $expectedCallback) {
if ($listener->getMetadatum('priority') == $expectedPriority) {
$found = true;
break;
}
}
}
$this->assertTrue($found, 'MvcEvent::EVENT_RENDER_ERROR not found');
}

public function testDefaultDisplayExceptions()
{
$this->assertTrue($this->strategy->displayExceptions(), 'displayExceptions should be true by default');
}

public function messageTokenProvider()
{
return array(
array(':className', true),
array(':message', true),
array(':code', false),
array(':file', true),
array(':line', true),
array(':stack', true),
);
}

/**
* @dataProvider messageTokenProvider
*/
public function testMessageTokens($token, $found)
{
if ($found) {
$this->assertContains($token, $this->strategy->getMessage(), sprintf('%s token not in message', $token));
} else {
$this->assertNotContains($token, $this->strategy->getMessage(), sprintf('%s token in message', $token));
}
}

public function previousMessageTokenProvider()
{
return array(
array(':className', true),
array(':message', true),
array(':code', false),
array(':file', true),
array(':line', true),
array(':stack', true),
array(':previous', true),
);
}

/**
* @dataProvider previousMessageTokenProvider
*/
public function testPreviousMessageTokens($token, $found)
{
if ($found) {
$this->assertContains($token, $this->strategy->getMessage(), sprintf('%s token not in previousMessage', $token));
} else {
$this->assertNotContains($token, $this->strategy->getMessage(), sprintf('%s token in previousMessage', $token));
}
}

public function testCanSetMessage()
{
$this->strategy->setMessage('something else');

$this->assertEquals('something else', $this->strategy->getMessage());
}

public function testCanSetPreviousMessage()
{
$this->strategy->setPreviousMessage('something else');

$this->assertEquals('something else', $this->strategy->getPreviousMessage());
}

public function testPrepareExceptionViewModelNoErrorInResultGetsSameResult()
{
$events = new EventManager();
$events->attachAggregate($this->strategy);

$event = new MvcEvent(MvcEvent::EVENT_DISPATCH_ERROR);

$event->setResult('something');
$this->assertEquals('something', $event->getResult(), 'When no error has been set on the event getResult should not be modified');
}

public function testPrepareExceptionViewModelResponseObjectInResultGetsSameResult()
{
$events = new EventManager();
$events->attachAggregate($this->strategy);

$event = new MvcEvent(MvcEvent::EVENT_DISPATCH_ERROR);

$result = new Response();
$event->setResult($result);
$this->assertEquals($result, $event->getResult(), 'When a response object has been set on the event getResult should not be modified');
}

public function testPrepareExceptionViewModelErrorsThatMustGetSameResult()
{
$errors = array(Application::ERROR_CONTROLLER_NOT_FOUND, Application::ERROR_CONTROLLER_INVALID, Application::ERROR_ROUTER_NO_MATCH);

foreach($errors as $error) {
$events = new EventManager();
$events->attachAggregate($this->strategy);

$exception = new \Exception('some exception');
$event = new MvcEvent(MvcEvent::EVENT_DISPATCH_ERROR, null, array('exception'=>$exception));
$event->setResult('something');
$event->setError($error);

$events->trigger($event, null, array('exception'=>$exception));

$this->assertEquals('something', $event->getResult(), sprintf('With an error of %s getResult should not be modified', $error));
}
}

public function testPrepareExceptionViewModelErrorException()
{
$errors = array(Application::ERROR_EXCEPTION, 'user-defined-error');

foreach($errors as $error) {
$events = new EventManager();
$events->attachAggregate($this->strategy);

$exception = new \Exception('message foo');
$event = new MvcEvent(MvcEvent::EVENT_DISPATCH_ERROR, null, array('exception'=>$exception));

$event->setError($error);

$this->strategy->prepareExceptionViewModel($event);

$this->assertInstanceOf('Zend\View\Model\ConsoleModel', $event->getResult());
$this->assertNotEquals('something', $event->getResult()->getResult(), sprintf('With an error of %s getResult should have been modified', $error));
$this->assertContains('message foo', $event->getResult()->getResult(), sprintf('With an error of %s getResult should have been modified', $error));
}
}

public function testPrepareExceptionRendersPreviousMessages()
{
$events = new EventManager();
$events->attachAggregate($this->strategy);

$messages = array('message foo', 'message bar', 'deepest message');
$exception = null;
$i = 0;
do {
$exception = new \Exception($messages[$i], null, $exception);
$i++;
} while($i < count($messages));

$event = new MvcEvent(MvcEvent::EVENT_DISPATCH_ERROR, null, array('exception'=>$exception));
$event->setError('user-defined-error');

$events->trigger($event, null, array('exception'=>$exception)); //$this->strategy->prepareExceptionViewModel($event);

foreach($messages as $message) {
$this->assertContains($message, $event->getResult()->getResult(), sprintf('Not all errors are rendered'));
}
}
}