Senior Software Engineer
THE ICONIC
Many of the concepts around Error and Exception handling are not fully understood or are ignored by the community.
Lets try to change that!
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.
An error is something that causes disruption to the normal flow of a computer program.
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.
<?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
<?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
Yes, you can suppress errors ಠ_ಠ
<?php
@fopen($file);
?>
<?php
if (file_exists($file)) {
fopen($file);
}
else {
die('File not found');
}
?>
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
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);
?>
An exception is an object that can be thrown, and caught within PHP
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)"
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)
PHP5 - First implemented
PHP5.1 - SPL library of exceptions introduced
PHP5.3 - Exception Nesting
PHP5.5 - Finally keyword added
public __construct (
[ string $message = ""
[, int $code = 0
[, Exception $previous = NULL ]]]
)
throw new Exception(
'An error occurred',
500,
new Exception('A previous error occurred')
);
<?php
try {
throw Exception('An error occurred');
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
}
// Caught exception: An error occurred
<?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
<?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'
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
Using namespaces?
Exception is the top of the exception hierachy
<?php
namespace myNamespace;
class foo {
function bar() {
throw new \Exception(‘An error occurred’);
}
}
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
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
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
- 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
<?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
Feel free to disagree!
The global namespace is too generic.
Use custom namespaced exceptions (MyCustomException).
SPL exceptions are more domain specific and provide a level of abstraction over the global exception namespace.
Not quite true, but only catch it in your default error handlers, not in normal program execution.
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
There is nothing wrong with this
Exception
|- LogicException
|- MyPackageException
|- MySubComponentException
|- MyFileHandlerException
Plan your exception hierarchy and follow that plan
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/
"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)
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
$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();
}
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
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
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
YAY!
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 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
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.
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 {}
?>
<?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)
}