I was getting an error about an exception thrown in a __toString() method in a class of mine, even though there was a try...catch() in there. I did some tracking down and it turns out an object within my instance was throwing an exception in its __toString(), which meant that it wasn't being caught by the containing class' catch!
I wrote some test code as a demonstration:
class test {
public function __toString() {
try {
$b = new b();
echo (string)$b;
} catch (exception $e) {
return (string)$e;
}
}
}
class b {
public function __toString() {
throw new Exception ("test");
}
}
$test = new test();
echo $test;
I had thought that exceptions always "bubbled up" through the code until they were caught or made it all the way out.
Is there any workaround for this? The instance within my class is from a library; I don't know if I can maintainably modify it to have a catch in its own __toString().
Per PHP's __toString() section in the magic methods docs:
Warning
You cannot throw an exception from within a __toString() method. Doing
so will result in a fatal error.
When executing the code snippet you gave you get a message that says the same thing:
PHP Fatal error: Method b::__toString() must not throw an exception
There's a feature request here to support this, which states that in current state of things it would be hard to implement.
Having said all that, the proper way to fix this is to submit a patch to your upstream library to not throw exceptions from that method.
If that's not possible, the workaround for you is instead of:
echo (string)$b;
write
echo $b->__toString();
In which case you can catch the exception as you expect.
Related
I cannot find a reliable source to ensure that I am able to throw an exception inside __destruct().
This is what php documentation says
Attempting to throw an exception from a destructor (called in the time of script termination) causes a fatal error.
But when I test it
class A
{
public function __destruct()
{
throw new \Exception();
}
}
try {
$a = new A();
} catch(\Exception $x) {
var_dump($x);
}
it seems that this is not true. I throw and catch exceptions normally. So, why has the doc got this line?
edited after Mark Baker's answer:
it actually works, the exception can be thrown and caught from destructor. I am still uncertain why the documentation lies then.
The destructor isn't being called anywhere in your script, so your try/catch block won't catch anything. If you unset($x) inside the try/catch block, then an exception will be thrown, and duly caught.
class A {
public function __destruct() {
throw new \Exception();
}
}
try {
$a = new A();
unset($a);
} catch(\Exception $x) {
var_dump($x);
}
Otherwise, PHP throws an exception when the script terminates, but that is outside of your try/catch block
EDIT
The exception thrown on script termination if your object hasn't been manually destroyed can't be caught because you can't wrap try/catch around the PHP's internal termination handling that destroys the object; and this is what results in a fatal error
Fatal Error: Uncaught Exception
So this is what the documentation is warning you about
This is more just for documentation, since I've already solved the issue, but it was subtle and difficult enough to debug that I thought it would be useful in the public sphere.
The issue was that I had a try/catch block in an object method that just wasn't working. The reduced example is in two files, which look like this:
TestClass.php:
<?php
//TestClass.php
namespace MyTest;
class TestClass {
public function __construct() {
\e("Initializing object");
try {
\e("Trying object exception");
\throwTestException("Failing gracefully in object");
\e("After exception");
} catch (Exception $e) {
\e($e->getMessage());
}
\e("After object init exception");
}
}
?>
Main.php:
<?php
//Main.php
function e($str) { echo "\n$str"; }
function throwTestException($msg) {
throw new RuntimeException($msg);
}
require "TestClass.php";
e("Beginning");
try {
e("First try");
throwTestException("Failing gracefully first");
e("After exception");
} catch (Exception $e) {
e($e->getMessage());
}
e("Ending");
e('');
e('Beginning object test');
new \MyTest\TestClass();
e('Ending object test');
?>
The expected result on loading Main.php was this:
Beginning
First try
Failing gracefully first
Ending
Beginning object test
Initializing object
Trying object exception
Failing gracefully in object
After object init exception
Ending object test
What I actually got was something like this:
Beginning
First try
Failing gracefully first
Ending
Beginning object test
Initializing object
Trying object exception
Fatal Error: Uncaught Exception: Failing gracefully in object......
As you can see, the exception was not being caught. I tried all sorts of things and just couldn't figure out why it wasn't being caught. And then.... (See answer below)
I realized it was a namespace issue. Because I had declared TestClass within the namespace MyTest, and throwTestException in the global namespace, my reference to Exception within the class method was tacitly resolving to \MyTest\Exception and thus NOT matching the actual exception being thrown, \RuntimeException. And since I wasn't actually trying to instantiate the exception from within the namespace, no "Unknown Class" errors emerged to reveal what was happening.
The solution, then, was simply to properly resolve the exception class I was trying to catch:
catch(\Exception $e) { .... }
To be fair, this became obvious as I built my highly reduced example. It wasn't obvious initially because the exception I was expecting to catch was being generated by the class's superclass (which was the SQLite3 class). Thus, I didn't have to worry about the namespace when generating the exception, and all I was thinking about when catching it was to use the most general form of exception, Exception. And again, since I wasn't instantiating that exception -- only matching against it in a catch block --, I didn't get any notices that it was unresolved.
What does $previous in the Exception constructor argument denote? How could I use it?
class MyException extends \Exception {
private $message;
private $code;
public function __construct($message,$code,\Exception $previous=null){
$this->message = $message;
$this->code = isset($code) ? $code : 0;
parent::__construct($message,$code,$previous);
}
}
I didn't find anything in the API Doc
If you throw an exception because you caught an exception, you can add the original exception as $previous. That means you can actually "nest" exceptions:
try {
throw new FooException('Foo exception');
} catch (FooException $e) {
$code = 1;
throw new BarException('Bar exception', $code, $e);
}
You can then loop over the exception "stack" instead of just the exception caught, providing you with more context.
while($e instanceof \Exception) {
echo $e->getMessage();
$e = $e->getPrevious();
}
Now, you would probably use this if you're implementing a library that can throw an exception, but you'd like to wrap that exception in your own. That way, your clients' code only needs to know of your exceptions, and not that of the exceptions that the dependencies of your code has, while not losing any kind of information.
the reason is that PHP 5.3 introduces nested exceptions as a default part of the PHP base Exception class. While the above code will work, if you are utilizing PHP 5.3, you can pass any previous exception as a third argument , and use the Exception::getPrevious() method to get a previously raised exception.
argument of a previous exception, allowing you to nest the exceptions. When preparing to log your exception, you can opt to iterate through any possible previously thrown and nested exceptions, and log any of the data you need.
What is nesting? Nesting is the ability to catch a particular exception, create a new exception object to be thrown with a reference to the original exception. This then allows the caller access to both the exception thrown from within the consumed library of the more well known type, but also access to the exception that originated this exceptional behavior as well.
Why is this useful? Typically, this is most useful in code that consumes other code that throws exceptions of its own type. This might be code that utilizes the
for more
http://www.brandonsavage.net/exceptional-php-nesting-exceptions-in-php/
I have this function in a class
public function doSomething()
{
try {
$result = functionCall();
if ($result === true) {
$this->doGoodResult('With Success Message');
} else {
$this->doBadResult('ERROR - unable to do something');
}
} catch (Exception $e) {
$this->doBadResult('Did you divide by zero?');
}
}
and I'm trying to test it with mocking out the extra functions
public function test_doSomthingWithBadResultGetsDoBadResultCalled()
{
$ajc = $this->getMockBuilder('MyClass')
->setMethods(array('doBadResult'))
->setConstructorArgs(array('doSomething', array('input_var' => 0)))
->getMock();
$ajc->expects($this->once())
->method('doBadResult')
->willReturn(null)
->with($this->contains('ERROR - unable to do something'));
$ajc->doSomething();
}
and the problem is that I'm getting the following error from PHPUnit
MyClass::doBadResult('Did you divide by zero?') was not expected to be called more than once.
I'm not sure why, but it looks like the catch statement is being executed only when in this mock scenario.
Could anyone help me figure out why this is happening and especially how to set this up so that my test doesn't fail?
After some debugging I found the Exception was Invalid argument supplied for foreach() and after some digging, it was in the $this->contains('ERROR...')
the answer is $this->contains should be $this->stringContains
To prevent your need to do more digging in the future. One of the problems that you have in your code is that you are catching the Base Exception type.
} catch (Exception $e) {
$this->doBadResult('Did you divide by zero?');
}
When an assertion fails in PHPUnit, a PHPUnit_Framework_AssertionFailedError is thrown and is caught by PHPUnit. This error extends the Base PHP Exception class. So inside your try block when the mock object gets called, it checks the parameters that were used. Since these are not correct, it throws the PHPUnit_Framework_AssertionFailedError which your catch statement grabs. Which then calls the method on your mock that wasn't expected to be called.
Because of this, it would be possible for your test to end up passing incorrectly. Because the failed assertion gets caught and handled by your code. You should have a specific exception that your catch is looking for and any other exceptions get passed on to the appropriate level.
While attempting to implement some kind of error handling when my __autoload() function fails to load a file I stumbled upon this little 'oddity'.
According to http://nl.php.net/autoload Exceptions thrown from within the __autoload() function can be caught in a catch block since PHP version 5.3+.
Note:
Prior to 5.3.0, exceptions thrown in the __autoload function could not be caught in the catch block and would result in a fatal error. From 5.3.0+ exceptions thrown in the __autoload function can be caught in the catch block, with 1 provision. If throwing a custom exception, then the custom exception class must be available. The __autoload function may be used recursively to autoload the custom exception class.
This works perfectly for the type of error handling I had in mind. The example below works just like I want it to (It throws an exception and it's caught):
function __autoload($class) {
throw new Exception();
}
try {
new UndefinedClass();
}
catch (Exception $e) {
echo 'damnit, it no work!';
}
Output: damnit, it no work!
If however I try the same concept with a static method call from a undefined class, Exceptions are not thrown, instead I get fed a fatal error.
try {
$a = UndefinedClass::someRandomStaticMethod();
}
catch (Exception $e) {
echo 'meh, it no work!';
}
Output: Fatal error: Class 'UndefinedClass' not found in ***** on line 16
The code above does not work. No Exception is thrown. ( same __autoload() function is used ).
http://nl.php.net/autoload mentions nothing about this usecase and this leaves me wondering if I'm doing something terribly wrong here? How can I make my __autoload() function throw exceptions on static method calls of a non-existent class?
If this is simply not possible with the __autoload() function, does spl_autoload() allow this kind of Exception throwing?
#Galled
Based on the link you provided I changed the __autoload() function to this:
function __autoload($class) {
eval('
class ' . $class . ' {
};
');
throw new Exception('Im an Exception!');
}
Using this version the fatal error in question is no longer fed to my monitor. However, it will now feed me with a different fatal error: the fact that the someRandomStaticMethod() doesn't exist.
I could of course include the declaration of the method within the eval() call. But this is not workable solution, as I would have to redeclare every class my project contains within the __autoload() function just to be able to avoid said fatal error. It's also interesting to know that no Exception is caught, as it seems the fatal error occurs before the Exception is handled, if it is even thrown in the first place.
I somewhat managed to solve the problem by expanding on the suggestions from Galled. After some more reading, especially here: http://nl.php.net/manual/en/function.spl-autoload-register.php#84086 I wrote the following.
function __autoload($class) {
...
/* if something and/or everything fails */
eval('
class ' . $class . ' {
public function __construct() {
throw new Exception(\'Im an Exception!\');
}
public static function __callstatic($method, $arguments) {
throw new Exception(\'Im an Exception!\');
}
};
');
}
This variation will now throw Exceptions on both use-cases. regardless on the existence of methods within a class.
While the above works, I'm hoping someone can provide a solution that doesn't touch the eval() function. As, for some reason, I feel like I just molested PHP.