PHP get an object from inside a callback - php

I want to get an errors object from a callback function. here is what i have done so far.
$errors = null;
set_exception_handler(function($exception) use (&$errors) {
$errors = $exception;
var_dump($exception); // works fine
});
var_dump($errors); // give null ????
throw new Exception("blablblablablabla", 0);
I am sure with an anonymous function and a variable would work very fine !
But in this case with a callback and object I am not sure what's going on ? i can't get my head a round. any help explanation would be really appreciated.

set_exception_handler does not stop execution. It just defines what will happen if/when an exception occurs, so you can var_dump($errors); errors after setting it. $errors will still be null at that point because you have set it to null, and nothing has happened yet to change that.
The thing that does stop execution is when an exception actually occurs and the anonymous function defined in set_exception_handler is called. At that point, the var_dump($exception); within that function is called, but any other code in the main program after the point where the exception occurred will not be executed.
Storing the exception in a global variable from within the exception handler doesn't make really sense, because if the exception handler is called, no more code in the main program will be executed, so you won't be able to use that variable anyway.

You are assigning the anonymous function as an exception handler with set_exception_handler().
Yet you do not throw an exception in your code. Therefore the anonymous function is never called, because there was no event to handle, which means that $error was never set.
If you add a try catch block your function will also not be called. This can be seen by the fact, that the line var_dump($exception) from within your handler is not called either.
If you want to store the last called exception in your code, you may need to use a wrapper function around that code, that uses try catch to overwrite a static resource with the last exception encountered.
Note, that the exception within the given function is never fully thrown, because it was catched by the wrapper!
class Catcher {
private static $_EXCEPTION = null;
public static function GET_EXCEPTION () {
return self::$_EXCEPTION;
}
public static function run($func) {
try {
return $func();
} catch (Exception $e) {
self::$_EXCEPTION = $e;
}
}
}
var_dump(Catcher::GET_EXCEPTION()); //dumps NULL
Catcher::run(function(){
throw new Exception('I was called');
});
var_dump(Catcher::GET_EXCEPTION()); //dumps the last thrown exception

Related

Exceptions don't bubble through instances in PHP?

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.

PHP chaining error handling

Is a good idea throw exceptions on chained methods?
For example:
class Mailer(){
private $attachment;
public function addAttachment($attachment){
if($this->validateAttachment($attachment)){
$this->attachment = $attachment;
}
throw new \InvalidArgumentException('Invalid attachment');
}
public function send(){
[...]
}
private function validateAttachment($attachment){
if($attachment === 'test'){
return true;
}
return false;
}
}
$mailer = new Mailer();
$mailer->addAttachment('invalid')->send();
Of course, this will fail and cause a fatal error unless we use try / catch.
Otherwise, if we don't thrown an error when addAttachment fails, use will not notice if anything went wrong.
And if the send can work without attachments, we cannot return an error on that method neither.
So, there is any good practice on error logging / handling when using chained methods?
You should throw an Exception anywhere you want to interrupt the flow of the program. It doesn't matter if it's chained or not. In your case, if the adding of the attachment fails, you want to stop it before it reaches send(). That's a perfectly fine use of an Exception.
Obviously, you need to make sure you wrap the whole execution in try/catch or you'll get a Fatal error (where all PHP stops).

PHPUnit Mocked method causes an exception

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.

__autoload fails to throw an Exception when it fails to load a class on a static method call

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.

how to set_error_handler so it can take effect only in an instance of a class and not globally?

consider this code:
class TestClass {
function __construct() {
set_error_handler(array($this,'error_handler'));
}
function error_handler($errno,$errstr,$errfile,$errline,$errcontext) {
trigger_error("\ncode: [$errno]\nfile: [$errfile -> $errline]\nSTR: [$errstr]",E_USER_ERROR);
}
}
count($nonExistentVariable);
//notice was thrown above but script carry on
//but here error handler is registered and...
$anInstance = new TestClass();
// rest of the code is affected by it
count($nonExistentVariable); // user error is thrown
So is there a way to make the error_handler function to fire only when error is encountered inside an instance and do not mess globally with other code ?
You could check the call stack in your error handler (via debug_backtrace()) and determine if it's coming from within the class you're interested in. If it's not coming from the class be sure to return FALSE and PHP will fall back on it's default handler.
http://php.net/manual/en/function.set-error-handler.php

Categories