Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Multi Exception Catch

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $6.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ok, let's talk about one more fun feature! And, surprise! This is also new in PHP 7.1.

For this example, let's have some fun. Imagine there's a guy at your office - Crazy Dave who always brings in amazing smelling cookies. But... he never shares them. Basically, Crazy Dave is a jerk.

So we're going to write a really annoying function that acts just like Crazy Dave. Open MainController and add a new public function cookiesAction(). Above, add @Route("/crazy-dave").

... lines 1 - 9
class MainController extends Controller
... lines 11 - 16
/**
* @Route("/crazy-dave")
*/
public function cookiesAction()
{
... lines 22 - 26
}
}

If you downloaded the code for this project, you should have a tutorial/ directory with an Exception directory inside. Copy that Exception directory into src/AppBundle.

This contains two new classes for the two reasons that Crazy Dave always gives us for not sharing his cookies: the NoCookiesLeft exception and the NoCookieForYou exception. That last one is particularly rude.

... lines 1 - 7
final class NoCookiesLeft extends \Exception
{
public function __construct($message = 'There are no more cookies :(', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

... lines 1 - 7
final class NoCookieForYou extends \Exception
{
public function __construct($message = 'No cookie for you!', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

See, Crazy Dave is such a jerk that he denies me reasonable cookie requests with random reasons. So, if random_int(0, 1), then he says throw new NoCookieForYou. Otherwise, he throws a new NoCookiesLeft exception. So disappointing.

... lines 1 - 9
class MainController extends Controller
{
... lines 12 - 19
public function cookiesAction()
{
if (random_int(0, 1)) {
throw new NoCookieForYou();
}
throw new NoCookiesLeft();
}
}

Find your browser and change the URL to /crazy-dave. Yep, random awful errors. And no cookies.

Catching Exceptions the Old Way

Ya know, we don't appreciate Dave shouting or throwing random things at us. So, let's at least ask Dave to whisper his denials. To do that, wrap all of the logic in a try-catch block. We need to catch both errors. No problem! First, catch NoCookieForYou. If that happens, set a new $whisper variable to Crazy Dave whispered and then $e->getMessage().

... lines 1 - 9
class MainController extends Controller
... lines 12 - 19
*/
public function cookiesAction()
{
try {
... lines 24 - 28
} catch (NoCookieForYou $e) {
$whisper = sprintf('Crazy Dave whispered "%s"', $e->getMessage());
} catch (NoCookiesLeft $e) {
$whisper = sprintf('Crazy Dave whispered "%s"', $e->getMessage());
}
return new Response('<html><body>'.$whisper.'</body></html>');
}
}

Here's the problem: the two exception classes do not extend a common exception class or interface... except for the base Exception class... and I don't want to catch all exceptions.

Yep, to responsibly catch both errors, we need to also catch NoCookiesLeft. And that's a bummer, because we need to duplicate the entire $whisper line. But, on the bright side, we can finally return a new Response() with the message inside.

When we refresh, this does fix the problem: Dave is now whispering. Still no cookies though... because Dave is still a jerk.

Catching Multiple Exceptions

If you find yourself in this situation, you should just go buy your own cookies. And if you find yourself catching multiple exceptions, there are two solutions. First, if you are able to modify these exceptions, you should make them extend a common base class or implement a common interface. Then, you can catch that exception instance. That's the proper solution.

But if you can't update the classes, in PHP 7, you can catch them both at once. Delete the second catch and instead, say NoCookieForYou | NoCookiesLeft $e. Say hello to the multi-catch syntax.

... lines 1 - 10
class MainController extends Controller
{
... lines 13 - 20
public function cookiesAction()
{
try {
... lines 24 - 28
} catch (NoCookieForYou | NoCookiesLeft $e) {
$whisper = sprintf('Crazy Dave whispered "%s"', $e->getMessage());
}
... lines 32 - 33
}
}

Thanks to that, I can refresh and it still works.

Alright guys, that is everything I want to show you from PHP 7. Yes yes, there are other things, like the spaceship operator... but they're not that important. And other improvements happen automatically. For example, the rand() function is now an alias to mt_rand(), which is a more secure way of generating random numbers. You get that for free. Thanks PHPeople!

And of course, free performance! Deploy it to your production server and watch your New Relic graphs get awesome! After all, isn't seeing performance graphs suddenly improve basically the best thing ever? Well, the best thing after cookies at least?

Alright guys, that's it for me. I'll seeya next time.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3, but all the concepts around PHP 7 are still ?valid.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.5.4
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.19
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.25
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "knplabs/knp-markdown-bundle": "^1.4", // 1.5.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.2.8
        "nelmio/alice": "^2.1", // v2.3.1
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "symfony/web-server-bundle": "3.3.*"
    }
}
userVoice