I have a class which handles a variety of database exceptions such as deadlocks and serialized transaction failures. I'm trying to unit test it and ran into a roadblock. An example of the code I want to test:
public function run(callable $callable)
{
$this->beginTransaction();
try {
$callable();
$this->commit();
} catch (\PDOException $e) {
if ($e->getCode() == '40P01' || $e->getCode() == '55P03') {
// Deadlock (40P01) or lock not available (55P03).
...
}
...
}
}
The documentation for PDOException says developers should not throw it themselves, and today I found out why when trying to write a unit test:
$obj->run(function() {
...
throw new \PDOException('Deadlock', '40P01');
});
A non well formed numeric value encountered. PDOException breaks the contract with Exception because exception codes must be int and PDOException creates string codes to match SQL standards. They should have made a separate SQLCode property but instead reused the built-in code. Therefore it's impossible to throw a PDOException with a real SQL error code within a unit test.
Next I tried to mock PDOException:
class PDOExceptionMock extends \PDOException
{
protected $code = '';
public function setCode($code)
{
$this->code = $code;
}
// This won't work because getCode is final
public function getCode()
{
return $this->code;
}
}
This won't compile because 'Cannot override final method Exception->getCode()'.
Since I can't (and don't want to) recreate every type of deadlock and transaction error using a real database, how can one write unit tests which need to throw PDOExceptions?
You don't need to overwrite getCode. It is already there, so you need just to set the code. E.g.
class A {
public function run(callable $callable)
{
try {
$callable();
} catch (\PDOException $e) {
if ($e->getCode() == '40P01' || $e->getCode() == '55P03') {
return 'expected';
}
return 'unexpected';
}
return 'no caught';
}
}
class StubException extends \PDOException {
public function __construct() {
parent::__construct();
$this->code = '40P01';
}
}
$a = new A;
echo $a->run(function(){throw new StubException;});
echoes 'expected';
Related
Let's say I'm coding a command. How would I stop it completely in the middle of it running?
Example:
public function handle()
{
if (!$this->good_times) {
$this->error('Bad times');
$this->exit();
}
// continue command stuff
}
I have tried:
throw new RuntimeException('Bad times');
But that dumps a bunch of ugliness in the terminal.
Just use a return statement instead of throwing an exception. Like...
public function handle()
{
if (!$this->good_times) {
$this->error('Bad times');
// $this->exit();
// throw new RuntimeException('Bad times');
return;
}
// ...
}
Ran into this problem as well.
Just create an exception that implements ExceptionInterface.
use Exception;
use Symfony\Component\Console\Exception\ExceptionInterface;
class ConsoleException extends Exception implements ExceptionInterface
{
}
now when you throw the error:
throw new ConsoleException('Bad times');
You get the error without the stack trace.
try to used like that
public function handle()
{
try {
if(!isset($x)){
throw new \Exception('not found');
}
}catch(\Exception $e){
exit($e->getMessage());
}
$this->info("test here");
}
private function foo()
{
if (/* exception trigger condition */) {
throw new \Exception('Exception message');
}
// ...
}
public function handle()
{
try{
$this->foo();
} catch (\Exception $error){
$this->error('An error occurred: ' . $error->getMessage());
return;
}
$this->comment('All done');
}
Use return with the exit code number:
public function handle()
{
$this->error('Invalid parameter: thiswillfail');
return 1;
}
Return a 0 if all is ok, a non-zero positive value on errors:
$ ./artisan mycommand thiswillfail; echo $?
Invalid parameter: thiswillfail
1
This works with at least Laravel 6 onwards.
You can use return or exit if you want to stop the command from within a function.
public function handle()
{
return;
}
protected function stop() {
exit;
}
public function handle()
{
$this->stop();
}
I want to handle custom Exceptions (PDOException, ...)
In App/Exceptions/Handler.php I add the following functions:
public function report(Exception $e)
{
if ($e instanceof \PDOException) {
//$this->renderHttpException($e);
//Doesn't work
}
else
{
parent::report($e);
}
}
public function render($request, Exception $e)
{
if($this->isHttpException($e))
{
return $this->renderHttpException($e);
}
else
{
return parent::render($request, $e);
}
}
protected function renderHttpException(HttpException $e)
{
if (view()->exists('errors.'.$e->getStatusCode()))
{
return response()->view('errors.'.$e->getStatusCode(), [], $e->getStatusCode());
}
else
{
//comes later
return null;
}
}
If I have a PDOException I can't handle it, because the PDOException doesn't look like a default Laravel Exception.
I know that it can't work, but I don't know how to make it work.
Here is the Laravel (5.1) Error:
Argument 1 passed to App\Exceptions\Handler::renderHttpException() must be
an instance of
Symfony\Component\HttpKernel\Exception\HttpException, instance of
PDOException given, called in /home/****/Workspace/****/app/Exceptions/Handler.php
on line 37 and defined
The function source is from
mattstauffer.co
I extended the exception class amongst other things to add a method getMessageHTML().
In my application I want to catch any exception - also exceptions of derived internal classes like e.g. the ReflectionException - and want to be able to use a getMessageHTML() method or other custom methods on any exception and any derived exception.
Is there any way, to add a method or trait to an internal class like the exception class or the ReflectionException class at runtime?
The only solution that comes to my mind is to wrap any catched exception into my extended exception class like:
$anyException = new Exception(); //or ReflectionException, or ...
$wrappedException = MyException::wrap($anyException);
$wrappedException->getMessageHTML(); //or any other custom method
is there any implementation, that allows to introduce a method to every derived internal or foreign class/object, so that any object knows it?
$anyException = new Exception(); //or ReflectionException, or ...
$anyException->getMessageHTML();
then i could simply do:
try
{
throw <anyException>(); //like throw Exception() or throw ReflectionException() ...
}
catch($e)
{
$e->getMessageHTML(); //its assured that the method is known.
}
For now I am doing it like this:
class MyException extends Exception
{
protected static function cast($destination, $sourceObject)
{
if(is_string($destination))
$destination = new $destination();
$sourceReflection = new \ReflectionObject($sourceObject);
$destinationReflection = new \ReflectionObject($destination);
$sourceProperties = $sourceReflection->getProperties();
foreach($sourceProperties as $sourceProperty)
{
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
if ($destinationReflection->hasProperty($name))
{
$propDest = $destinationReflection->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($destination,$value);
}
else
$destination->$name = $value;
}
return $destination;
}
public static function wrap(Exception $exception)
{
$wrap = $exception;
if(!$exception instanceof MyException)
$wrap = self::cast(__CLASS__, $exception);
return $wrap;
}
public function getMessageHTML()
{
//some code ...
}
}
try
{
throw new ReflectionException('test');
}
catch(Exception $e)
{
$e = MyException::wrap($e);
echo $e->getMessageHTML();
}
or - simpler - and with the advantage to have the previous exception available:
class MyException extends Exception
{
public static function wrap(Exception $exception)
{
$wrap = $exception;
if(!$exception instanceof AppException)
{
try
{
throw new AppException($exception->getMessage(), $exception->getCode(), $exception);
}
catch(AppException $e)
{
$wrap = $e;
}
}
return $wrap;
}
public function getMessageHTML()
{
//some code ...
}
}
I've written a PHPUnit test that checks if an exception is thrown from a closure when a method is invoked. The closure function is passed in as an argument to the method with an exception being thrown from it.
public function testExceptionThrownFromClosure()
{
try {
$this->_externalResourceTemplate->get(
$this->_expectedUrl,
$this->_paramsOne,
function ($anything) {
throw new Some_Exception('message');
}
);
$this->fail("Expected exception has not been found");
} catch (Some_Exception $e) {
var_dump($e->getMessage()); die;
}
}
The code for the get function specified on the ExternalResourceTemplate is
public function get($url, $params, $closure)
{
try {
$this->_getHttpClient()->setUri($url);
foreach ($params as $key => $value) {
$this->_getHttpClient()->setParameterGet($key, $value);
}
$response = $this->_getHttpClient()->request();
return $closure($response->getBody());
} catch (Exception $e) {
//Log
//Monitor
}
}
Any ideas why the fail assert statement is called? Can you not catch exceptions thrown from closures in PHP or is there a specific way of dealing with them I don't know about.
For me the exception should just propagate out the return stack, but it doesn't appear to. Is this a bug? FYI I'm running PHP 5.3.3
Thanks for the answers...
Managed to figure out the issue. It looks like the problem is that the try-catch block that's being invoked is the one where the closure is invoked. Which makes sense...
So the code above should be
public function get($url, $params, $closure)
{
try {
$this->_getHttpClient()->setUri($url);
foreach ($params as $key => $value) {
$this->_getHttpClient()->setParameterGet($key, $value);
}
$response = $this->_getHttpClient()->request();
return $closure($response->getBody());
} catch (Exception $e) {
//Log
//Monitor
throw new Some_Specific_Exception("Exception is actually caught here");
}
}
So it looks like PHP 5.3.3 doesn't have a bug after all which was mentioned. My mistake.
I cannot reproduce the behavior, my example script
<?php
class Some_Exception extends Exception { }
echo 'php ', phpversion(), "\n";
$foo = new Foo;
$foo->testExceptionThrownFromClosure();
class Foo {
public function __construct() {
$this->_externalResourceTemplate = new Bar();
$this->_expectedUrl = '_expectedUrl';
$this->_paramsOne = '_paramsOne';
}
public function testExceptionThrownFromClosure()
{
try {
$this->_externalResourceTemplate->get(
$this->_expectedUrl,
$this->_paramsOne,
function ($anything) {
throw new Some_Exception('message');
}
);
$this->fail("Expected exception has not been found");
} catch (Some_Exception $e) {
var_dump('my exception handler', $e->getMessage()); die;
}
}
}
class Bar {
public function get($url, $p, $fn) {
$fn(1);
}
}
prints
php 5.4.7
string(20) "my exception handler"
string(7) "message"
as expected
So I was reading the php manual on Extending Exceptions and read the example code. My question about the following code is: why does var_dump($o) evaluate to null? Is it because the constructor of the class TestException throws an exception, hence not allowing the completion of the object? I am almost certain that is the reason.
Nevertheless here is the code for examination:
<?php
/**
* Define a custom exception class
*/
class MyException extends Exception
{
// Redefine the exception so message isn't optional
public function __construct($message, $code = 0, Exception $previous = null) {
// some code
// make sure everything is assigned properly
parent::__construct($message, $code, $previous);
}
// custom string representation of object
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
public function customFunction() {
echo "A custom function for this type of exception\n";
}
}
/**
* Create a class to test the exception
*/
class TestException
{
public $var;
const THROW_NONE = 0;
const THROW_CUSTOM = 1;
const THROW_DEFAULT = 2;
function __construct($avalue = self::THROW_NONE) {
switch ($avalue) {
case self::THROW_CUSTOM:
// throw custom exception
throw new MyException('1 is an invalid parameter', 5);
break;
case self::THROW_DEFAULT:
// throw default one.
throw new Exception('2 is not allowed as a parameter', 6);
break;
default:
// No exception, object will be created.
$this->var = $avalue;
break;
}
}
}
// Example 1
try {
$o = new TestException(TestException::THROW_CUSTOM);
} catch (MyException $e) { // Will be caught
echo "Caught my exception\n", $e;
$e->customFunction();
} catch (Exception $e) { // Skipped
echo "Caught Default Exception\n", $e;
}
// Continue execution
var_dump($o); // Null
?>
Have a look at PHP exception from PHP official site.
Example:
<?php
class MyException extends Exception { }
class Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
/* rethrow it */
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}
$foo = new Test;
$foo->testing();
?>