You may be shocked by what I’m about to say but here it is: PHP is fucked up.
Oh, no. Wait. No you’re not shocked. You already knew it.
Today’s topic: namespaces.
PHP’s namespace implementation is fucked up. There’s really no way to say it nicely, and you will realize it 5 minutes after starting to use them, when most of your code breaks and you need to litter the beginning of your files with use
statements and you really wonder what you gained in the process. It really comes down to the impossibility to import a whole namespace, and PHP’s inability to look into parent namespaces when a class is not found in the current one. Sure, I understand the first feature is actually very tricky in a dynamic language, and the second one may have been rejected because of performance reasons, but with that many downsides you have to wonder why namespaces were implemented at all. Pornel gives a good list of all those downsides in his article on PHP namespaces.
Sure, namespaces let us get rid of the PEAR
naming conventions, and they work nicely with spl_autoload_register
, but they also gave me my biggest WTF moment in recent history.
Consider the following code:
namespace Foo;
class BarException extends Exception
{
}
class Bar
{
public function trySomethingSafe()
{
try
{
throw new BarException("Catch this!");
}
catch (Exception $e)
{
echo "Caught exception: ".$e->getMessage();
}
}
}
It defines a new exception type (FooException
), and a class (Bar
) that does something safe by wrapping the code of the function inside a try/catch
statement.
Now run this from another file:
require_once 'foobar.php';
$b = new FooBar();
try
{
$b->trySomethingSafe();
}
catch (Exception $e)
{
echo "This should never show up, right?";
}
Well, guess what you get in your output window? Yep:
This should never show up, right?
It’s like the first try/catch
block is not catching the exception! Surely it can’t be a bug in the PHP runtime, right?
Well, of course not. It’s the user’s fault. And that’s because PHP’s namespaces suck ass.
Look at the catch
statement in trySomethingSafe
: it’s meant to catch all types of exceptions by specifying the top parent Exception
type, but this is actually not it. See, that code is inside namespace Foo
, and I didn’t specify either a use Exception
at the top of the file, nor did I use Exception
’s full name, Exception
. Yes, that leading backslash is really super important in this case, because it makes all the difference between what you meant (the standard Exception
type) and what PHP understands (a completely imaginary FooException
type).
To fix the problem, replace the catch
clause in trySomethingSafe
with this:
You will then get the expected result:
Caught exception: Catch this!
The tricky thing here is that PHP doesn’t seem to care that FooException
doesn’t exist. It happily runs the script, gets the exception, which is of type FooBarException
, figures out that the class name does not equal to FooException
, and bubbles the exception up the call stack. It doesn’t stop to check that the exception type we want to catch actually exists.
So, yeah. You need to be careful with PHP namespaces, especially when you add them to a previously un-namespaced project.