PHP Exception Handling

 

 

About Me

Ollie Brennan

Senior Software Engineer
THE ICONIC

@BrOllieGitHub.com/olliebrennan

Why am I doing this talk?

 

Many of the concepts around Error and Exception handling are not fully understood or are ignored by the community.

 

Lets try to change that!

Part1: Error Handling

What is error handling?

Error handling refers to the anticipation, detection, and resolution of programming, application, and communications errors.

 

Specialized programs, called "error handlers", are available for some applications.

What is an error exactly?

An error is something that causes disruption to the normal flow of a computer program.

Error Severity

E_ERROR - Fatal run-time errors. These cannot be recovered from, such as memory allocation problem. Execution of the script is halted.

E_WARNING - Run-time warnings (non-fatal errors). Execution of the script is not halted

E_NOTICE - Run-time notices. Indicate that the script encounted something that could indicate an error, but could also happen in the normal course of running a script.

E_STRICT - Enable to have PHP suggest changes to your code which will ensure the best interoperability and forward compatibility of your code.

http://php.net/manual/en/errorfunc.constants.php

Non-fatal example


<?php
$x = 1;

echo "Result: " . $x + $y; // 1
?>
            

# PHP Notice: Undefined variable: y in /path/to/test.php on line 4
# PHP Stack trace:
# PHP   1. {main}() /Users/ollie/test.php:0
            

Fatal example


<?php
function call_method($obj) {
    $obj->method();
}

call_method(null); // oops!
?>
            

# Fatal error: Call to a member function method() on a non-object
# in /path/file.php on line 6
            

Error Suppression

Yes, you can suppress errors  ಠ_ಠ


<?php
@fopen($file);
?>
            

OR


<?php
if (file_exists($file)) {
    fopen($file);
}
else {
    die('File not found');
}
?>
            

Development Settings

Turn it ALL on. If you want to build forward compatible code, you want all the information you can get


<?php
// Logs everything
error_reporting(E_ALL);

// Shows all errors to screen
ini_set('display_errors', 1);
?>
            

- Get warned of deprecated code
- Get warned of notices
- Get warned on potential bugs
- etc

Production Settings

Log everything, but keep displaying of errors off*


<?php
// Logs everything except deprecated notices
error_reporting(E_ALL & ~E_DEPRECATED);

// Shows no errors
ini_set('display_errors', 0);
?>
            

Part2: Exception Handling

What is an exception

 

An exception is an object that can be thrown, and caught within PHP

 

 

http://php.net/manual/en/language.exceptions.php

Golden Rule

Michael Nitschinger defines their use perfectly

"Use for what their name says: exceptional situations. Don't use exceptions to control the flow of your application logic (like substituting if-statements or to control loops)"

 

 

http://nitschinger.at/A-primer-on-PHP-exceptions

To Clarify

Can your code continue along a normal path?

- If so, do not use an exception (e.g. form validation).
- If not, throw one (e.g. Unable to Connect to DB)

History of exceptions in PHP

PHP5 - First implemented

PHP5.1 - SPL library of exceptions introduced

PHP5.3 - Exception Nesting

PHP5.5 - Finally keyword added

Anatomy of an Exception


public __construct (
    [ string $message = ""
    [, int $code = 0
    [, Exception $previous = NULL ]]]
)
            

throw new Exception(
    'An error occurred',
    500,
    new Exception('A previous error occurred')
);
            

Syntax example (1/3)


<?php
try {
    throw Exception('An error occurred');
} catch (Exception $e) {
    echo 'Caught exception: ' . $e->getMessage();
}

// Caught exception: An error occurred
            

Syntax example (2/3)


<?php
try {
    throw Exception('An error occurred');
} catch (Exception $e) {
    echo 'Caught exception: ' . $e->getMessage();
} finally {
    echo 'Finally print this';
}

// Caught exception: An error occurred
// Finally print this
            

Syntax example (3/3)

Interestingly...


<?php
try {
    throw Exception('An error occurred');
} finally {
    echo 'Finally print this';
}

// Finally print this
// Fatal error: Uncaught exception 'Exception' with message 'An error occurred'
            

https://wiki.php.net/rfc/finally#proposal

Exceptions are objects

Although common practice: you do not have to throw exceptions right away.

Treat them as objects, attach additional information as needed*


<?php

    $exception = new Exception('An error occurred');
    $exception->timestamp = time();
    $exception->username = 'Admin';

    throw $exception;

?>
                

*useful to add custom logging information to your apps

What is “\Exception"

Using namespaces?
Exception is the top of the exception hierachy


<?php
namespace myNamespace;

class foo {
    function bar() {
        throw new \Exception(‘An error occurred’);
    }
}
            

SPL Exceptions

SPL provides a set of standard Exceptions for your use.


                Exception
                    |- BadFunctionCallException implements Exception
                    |- BadMethodCallException extends Exception
                    |- DomainException extends Exception
                    |- InvalidArgumentException extends Exception
                    |- ...
                    |- UnexpectedValueException
            

http://devzone.zend.com/1075/the-standard-php-library-spl/#section4-1

Exception Abstraction

To give your code meaning, create your own exceptions and only catch what you need to


<?php

class DidNotRsvpException extends DomainException {}

try {
    throw new DidNotRsvpException('SydPHPer did not RSVP');
} catch (DidNotRsvpException ($e) {
    echo 'UhOh: ' . $e->getMessage();
}

// UhOh: Member did not RSVP
            

Exception Bubbling

Catch your custom exceptions


try {
    throw new DidNotRsvpException('SydPHPer did not RSVP');
} catch (DidNotRsvpException ($e) {
    echo 'UhOh: ' . $e->getMessage(); // UhOh: Member did not RSVP
}
            

try {
    throw new DidNotRsvpException('SydPHPer did not RSVP');
} catch (DomainException ($e) {
    echo 'UhOh: ' . $e->getMessage(); // UhOh: Member did not RSVP
}
            

try {
    throw new DidNotRsvpException('SydPHPer did not RSVP');
} catch (Exception ($e) {
    echo 'UhOh: ' . $e->getMessage(); // UhOh: Member did not RSVP
}
            

DidNotRsvpException extends
DomainException
extends
Exception

Default Exception Handlers

- Catch exceptions that bubble all the way up to top scope
- Execution will stop after the exception_handler is called
- Useful for displaying 500 pages or adding custom logging

Default Exception Handlers


<?php

function exception_handler($exception) {
    echo "Uncaught exception: " $exception->getMessage();
}

set_exception_handler('exception_handler');

throw new Exception('Uncaught Exception');
echo "Not Executed";
            

// Uncaught exception: Uncaught Exception
            

http://php.net/set_exception_handler

Part3: Best Practices

 

Feel free to disagree!

Never throw \Exception

The global namespace is too generic.
Use custom namespaced exceptions (MyCustomException).

But you may throw SPL exceptions

SPL exceptions are more domain specific and provide a level of abstraction over the global exception namespace.

Never catch \Exception

Not quite true, but only catch it in your default error handlers, not in normal program execution.

Always Extend SPL

SPL provides a level of built in abstraction which \Exception does not

 

I would recommend extending SPL from any custom exceptions


<?php

class DidNotRsvpException extends DomainException {} // GOOD

class DidNotRsvpException extends Exception () // BAD
            

Create a Hierarchy

There is nothing wrong with this


                Exception
                |- LogicException
                    |- MyPackageException
                        |- MySubComponentException
                            |- MyFileHandlerException
            

 

Plan your exception hierarchy and follow that plan

Honor Layer Abstraction

Do not let custom logic bubble up too far.
Catch and rethrow as appropriate


<?php
try {
    // some PDO action here
} catch(PDOException $pdoE) {
    throw new ControllerException(
        'There was an error: ' . $pdoE->getMessage(),
        null,
        $pdoE // note nesting of $pdoE
    );
}
            

If the PDO exception is caused by your controller, allowing the PDO exception to bubble up would be a violation of layer of abstraction

Brandon Savage said so: https://www.brandonsavage.net/exceptional-php-introduction-to-exceptions/

Leaky Abstraction

"Details of your model leak out when you throw a custom exception that is uncaught requiring the underlying software to understand your underlying systems"

 

Extend generic exceptions and/or SPL exceptions to mitigate (DatabaseQueryException extends ModelStorageException extends LogicException)

 

https://en.wikipedia.org/wiki/Leaky_abstraction

Exceptions are meant to be handled

The idea of OOP is that you throw exceptions in exceptional circumstances & also catch them.

 

Just because you can ignore them, does not mean you should

Exceptions are here to help you tidy up


$db = new PDO($dsn, $user, $password);
$db->beginTransaction();

try {
    $db->exec("delete from table");
    $db->exec("insert into tabletwo");
    $db->commit();
} catch (PDOException $e) {
    $db->rollBack();
}
            

Exceptions should be informative

Self explanatory but:
give exception messages as much information as possible


throw new DidNotRsvpException('User present but did not RSVP'); // GOOD

throw new DidNotRsvpException('Error'); // BAD
            

Exceptions are not meant to be silenced


try {
    throw new DidNotRsvpException('User present but did not RSVP');
} catch (DidNotRsvpException $e) {
    // Do nothing
}
            

If done incorrectly I guarantee one day someone will curse your name

The ICONIC in production

We fail and show a maintenance page on ALL errors
(including warnings excluding deprecated)

 

Probably not good practice because it breaks the user experience BUT all issues are fixed very quickly

Part4: PHP7

 

YAY!

Exceptions in the engine (1/2)

Exceptions in the engine coverts many fatal and recoverable fatal errors into exceptions.


<?php
function call_method($obj) {
    $obj->method();
}

call_method(null); // oops!
?>
            

# Fatal error: Call to a member function method() on a non-object
# in /path/file.php on line 6
            

Exceptions in the engine (2/2)

Exceptions in the engine coverts many fatal and recoverable fatal errors into exceptions.


<?php
try {
    call_method(null); // oops!
} catch (EngineException $e) {
    echo "Exception: {$e->getMessage()}\n";
}
?>
                

# Exception: Call to a member function method() on a non-object
                

New Exception Hierarchy

 


                interface Throwable
                    |- Exception implements Throwable
                        |- ...
                    |- Error implements Throwable
                        |- TypeError extends Error
                        |- ParseError extends Error
                        |- AssertionError extends Error
            

 

Implemented to allow for backward compatibility.
You do not want PHP5.x code catching these new "Errors" with unexpected consequences.

Throwable Interface

A new "Throwable" interface which allows for a class to be "thrown" has been implemented.

 

May not be used in userland, therefore all user exceptions must extend Exception accordingly.

 


<?php
    class Exception implements Throwable {}
?>
            

Bringing it all together

 


<?php
function add(int $left, int $right) {
    return $left + $right;
}

try {
    echo add('left', 'right');
} catch (Exception $e) {
    // Handle exception
} catch (Error $e) { // Clearly a different type of object
    // Log error and end gracefully
} catch (Throwable $e) {
    // Will catch any throwable class (Exception or Error)
}
            

Questions?

 

 

 

Images courtesy of https://unsplash.com/