I witnessed some strange behaviour regarding PHP's exception handling in a recent project. Case goes as follows.
In my app, I use namespaces. All classes are in individual source code files. The code relevant to this particular case, is spread over 3 classes.
The "outermost" class is a dispatcher (or router), which wraps the dispatch call inside a try-catch block. The dispatched request, calls a method in a third class, which runs code (wrapped in a try-catch block), which causes an exception.
Because I had omitted a use Exception; statement in the class where the error happens, the thrown exception trickles all the way back to the outermost layer (the dispatcher), where it is caught - causing me to scratch my head why the catch around the code causing the error isn't working.
To me this seems strange. Logically, PHP should in this situation (IMO) throw a Class not found exception/error, leading me to the actual error in my code, instead of trying to "stay alive" as long as possible.
Should this be filed as a bug, or is this expected behaviour?
Edit: Code example
File: class-a.php
<?php
namespace hello\world;
class classA {
protected $b;
public function __construct() {
$this->b = new \hello\world\classB();
}
public function doSomething() {
try {
$this->b->throwException();
} catch (Exception $e) {
}
}
}
File: class-b.php
<?php
namespace hello\world;
class classB
{
public function throwException() {
throw new \Exception("bar closed");
}
}
File: run.php
<?php
include 'class-a.php';
include 'class-b.php';
$a = new \hello\world\classA();
$a->doSomething();
ClassB throws an \Exception in ClassB::doSomething(), for which ClassA has a catch-clause, but because ClassA doesn't declare use Exception or catch (\Exception), the catch doesn't match and execution ends with a Uncaught exception error1. But in my opinion, it should cause a Class not found error.
I might be expecting too much of the permissive PHP compiler, but it would help in tracking down silly errors that should be easy for the compiler to spot.
1 If the $a->doSomething() in run.php was surrounded by a try..catch clause, the Exception would (or at least could) be caught there, since it trickles down the stack.
PHP's exception catching mechanism does not validate that the class you catch actually exists.
It exhibits the same behavior when using typehinting in functions, so I suspect it merely converts the exception/function type hint into a string or something and compares that with the type of the relevant object.
Whether this is a bug or not is questionable. Personally I think it should be classified as a bug, but PHP has all sorts of wonky behaviors :D
Related
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.
Hi devs
I have a simple PHP OO project. I have an exception who appears 2 times.
Something like :
if (!$x) {
throw new Exception("Don't use this class here.");
}
I want to dev a class in order to edit this code like that :
if (!$x) {
throw new ClassUsageException();
}
How to dev the Excpetion class with default Exception message ?
Thanks
I'd advise creating new exception classes sparsly. It is no fun to check a multitude of exceptions left and right. And if you really feel the need, check what kinds of exceptions are already defined and where in that hierarchy your exception will fit and then extend that class, i.e. give the developers a chance to catch a (meaningful) range of exceptions without having to explicitly write one catch-block after the other.
I'm really not sure what you're trying to achieve here with Don't use this class here. but it could be an InvalidArgumentException (or something derived from that exception, if you really must). There are other mechanisms to prevent an instance of a certain class at a specific place though.
You can extend the Exception class
<?php
Class ClassUsageException extends Exception {
public function __construct($msg = "Don't use this class here.", $code = 0) {
parent::__construct($msg, $code); //Construct the parent thus Exception.
}
}
try {
throw new ClassUsageException();
} catch(ClassUsageException $e) {
echo $e->getMessage(); //Returns Don't use this class here.
}
From my previous question I understand that isn't a good practice manage each exception with try/catch block, 'cause if I've hundred functions I should put hundred try/catch block.
Now I noticed the set_error_handler function, if I've understand correctly (never used it), allow me to swith in a file or function all the error generated in the whole scripts. So instead of put try catch block this function should automatically intercept the error and call a function, is right?
Now I already have a Log class that help me to write a stack trace in a file. This file is daily so I can see all system transaction in separated file.
My Log class is a SingleTon, so in each classes if I want write some trace in the log file I just need to do this:
Log::warning('some parameter here');
My goal is create an error.php file where all the error are switched in the Log::warning('...');. I think that this set_error_handler should be placed in the system core. As I said I never worked with it, someone could help me to achieve this with a bit example? I'll be glad.
set_error_handler is used to handle errors in a script not exceptions.
If you want to catch all exceptions from your application to apply the same process you have to call set_exception_handler PHPDoc.
This function takes a callable in argument, so your handler must be defined in another function.
The main difference between this function and a try catch block is that
Execution will stop after the exception_handler is called.
It's a also good practice to keep existing exception handlers possibly introduce by an included lib.
You can create a class to do this
class ErrorHandler
{
private $previousExceptionHandler;
public function registerExceptionHandler($callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function handleException(\Exception $e)
{
// DO YOUR STUFF
if ($this->previousExceptionHandler) {
call_user_func($this->previousExceptionHandler, $e);
}
}
}
And to use it
$errorHandler = new ErrorHandler();
$errorHandler->registerExceptionHandler();
There are some good libs to do that and more, especially if you want to catch your exceptions for logging purpose. You can try the excellent Monolog lib wihich is widely used, and its ErrorHandler class
I have exception class:
class MartbooksException extends Exception
{
public function __construct($msg)
{
parent::__construct($msg, 0, null);
echo $msg;
Kohana::$log->add(Log::ERROR, $msg);
}
}
Kohana::$log->add(Log::ERROR, $msg);
Is this all I should do to write logs in application/logs files?
Is this good solution?
To follow the Kohana style I would recommend that you use:
Class Martbooks_Exception Extends Kohana_Exception
as declaration and place the file with name exception.php in classes/martbooks. This follows the style of Kohana.
Extending Kohana_Exception instead of Exception allows you to use variable substitution along the lines of
throw new Martbooks_Exception ('this is a :v', array (':v' => 'variable', ));
As for the defining a __construct()-method, the echo $msg; part would not be my preferred way of solving error handling, any echoing should be done in the block that catches the exception. The same could be argued for in the case of calling Kohana::$log->add(), but if you want log every Martbooks_Exception, your solution is perfectly valid. In that case I would rewrite your code to:
Class Martbooks_Exception Extends Kohana_Exception
{
public function __construct($message, array $variables = NULL, $code = 0)
{
parent::__construct ($message, $variables, $code);
Kohana::$log->add (Log::ERROR, __ ($message, $variables));
}
}
with a definition of __construct() that conforms to Kohana_Exception's __construct().
The only objection that I would have against logging with Log::ERROR level in the constructor is that it assumes that every exception is an application level error, which might be true of some exception types, but it could also be used to signal other meanings. The exact meaning of an exception should be left to the exception handling block.
To add a log message all you need is a single line indeed; Kohana::$log->add($level, $message[, $values]) see the api
Besides that, I don't think this is a valid solution. You had better create your own exception handler, as you can see on this page of the userguide
Essentially I have a method of a class called killProgram, which is intended to send a hTTP redirect and then kill PHP.
How am I supposed to test this? When I run phpunit it doesn't return anything for that test, and closes completely.
Right now I'm considering having the killProgram function throw an exception which shouldn't get handled, which would allow me to assert that an exception was thrown.
Is there a better way?
It's obviously an old question but my suggestion would be to move the code that die()'s into a separate method that you can then mock.
As an example, instead of having this:
class SomeClass
{
public function do()
{
exit(1);
// or
die('Message');
}
}
do this:
class SomeClass
{
public function do()
{
$this->terminate(123);
// or
$this->terminate('Message');
}
protected function terminate($code = 0)
{
exit($code);
}
// or
protected function terminate($message = '')
{
die($message);
}
}
That way you can easily mock the terminate method and you don't have to worry about the script terminating without you being able to catch it.
Your test would look something like this:
class SomeClassTest extends \PHPUnit_Framework_TestCase
{
/**
* #expectedExceptionCode 123
*/
public function testDoFail()
{
$mock = $this->getMock('SomeClass');
$mock->expects($this->any())
->method('terminate')
->will($this->returnCallback(function($code) {
throw new \Exception($code);
}));
// run to fail
$mock->do();
}
}
I haven't tested the code but should be pretty close to a working state.
As every tests are run by the same PHPUnit process, if you use exit/die in your PHP code, you will kill everything -- as you noticed ^^
So, you have to find another solution, yes -- like returning instead of dying ; or throwing an exception (you can test if some tested code has thrown an expected exception).
Maybe PHPUnit 3.4 and it's --process-isolation switch (see Optionally execute each test using a separate PHP process) might help (by not having everything dying), but you still wouldn't be able to get the result of the test, if PHPUnit doesn't get the control back.
I've had this problem a couple of times ; solved it by returning instead of dying -- even returning several times, if needed, to go back "high enough" in the call stack ^^
In the end, I suppose I don't have any "die" anymore in my application... It's probably better, when thinking about MVC, btw.
There's no need to change the code just to be able to test it, you can simply use set_exit_overload() (provided by test_helpers from same author as PHPUnit).
I realise you've already accepted an answer for this and it's an old question, but I figure this might be useful for someone, so here goes:
Instead of using die(), you could use throw new RuntimeException() (or an exception class of your own), which will also halt program execution (albeit in a different fashion) and use PHPUnit's setExpectedException() to catch it. If you want your script to die() when that exception is encountered, printing absolutely nothing up at level of the user, take a look at set_exception_handler().
Specifically, I'm thinking of a scenario in which you'd place the set_exception_handler()-call into a bootstrap file that the tests don't use, so the handler won't fire there regardless of scenario, so nothing interferes with PHPUnit's native exception handling.
This relates to set of issues I've been having getting some legacy code to pass a test. So I've come up with a Testable class like this...
class Testable {
static function exitphp() {
if (defined('UNIT_TESTING')) {
throw new TestingPhpExitException();
} else {
exit();
}
}
}
Now I simply replace calls to exit() with Testable::exitphp().
If it's under test I just define UNIT_TESTING, in production I don't. Seems like a simple Mock.
You can kill the script or throw an exception, depending on the value of an environmental variable...
So you kill in production or throw an exception in test environment.
Any call to die or exit, Kills the whole process...
This was supposed to be a comment but I can't comment with the level of my reputation points.