Dependency injection in Symfony
You work with Symfony, but the concept of dependency injection is a little blurry for you? Find out how to take advantage of the component reading this article.
PHP 7 brings some changes about how errors are reported.
From now on, most of the errors are reported through the exception class Error
.
All Throwable
will bubble up through the execution stack until they meet one of these cases:
catch
block which supports this kind of error;set_exception_handler()
;We are going to first define, and then see how to use Throwable
, Error
and Exception
.
Throwable
is a PHP 7 interface which represents an error.
interface Throwable
{
public function getMessage(): string; // Error reason
public function getCode(): int; // Error code
public function getFile(): string; // Error begin file
public function getLine(): int; // Error begin line
public function getTrace(): array; // Return stack trace as array like debug_backtrace()
public function getTraceAsString(): string; // Return stack trace as string
public function getPrevious(): Throwable; // Return previous `Trowable`
public function __toString(): string; // Convert into string
}
Errors
and Exceptions
are implementing Throwable
.
Here is Throwable
hierarchy:
interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException
⚠ Caution! You can only implement
Throwable
throughError
andException
. Else you get a FATAL errorPHP Fatal error: Class MyClass cannot implement interface Throwable, extend Exception or Error instead
But you can extend this interface in user space
interface MyThrowable extends Throwable {
public function myCustomMethod();
}
class MyException extends Exception implements MyThrowable {
public function myCustomMethod()
{
// implement custom method code
}
}
Error
is the base class of all the internal PHP errors.
The most commons are the following:
ParseError
, which is thrown when we require
or eval
a code with syntax error.TypeError
, which is thrown when arguments/return do not match its declare type. It's also the case when an invalid number of arguments are passed to php built-in function in strict mode
._Sometimes you have the right to throw Error
in your code, for example if you parse a file that contains a syntax error, or if you pass wrong numbers of arguments to a variadic function.
Exception
is the user exception base class.
Very often you have to throw or create one, se we are going to see how it works and how use to it properly.
You have to use the throw
keyword in order to throw an Exception
.
throw new Exception('Render error.');
echo 'Example text.';
An
Exception
interrupts the execution of next instructions. In this example theecho
won't be called.
You have to use try
catch
structure.
try {
if (!$_GET['title']) {
throw new Exception('Can't show title. Title is required.');
}
echo $_GET['title'];
} catch (Exception $e) {
echo '⚠ Exception appear: ' . $e->getMessage();
}
> The script will show the title if it's provided, or else it will show the error message.
You can attach multiple `catch` to a `try` bloc in order to split different exception types.
You must respect catch block precedence.
```php
try {
if (!$_GET['title']) {
throw new Exception('Can't show title. Title is required.');
}
if (!is_string($_GET['title'])) {
throw new RuntimeException('Title must be a string.');
}
echo $_GET['title'];
} catch (RuntimeException $e) {
echo $e->getMessage();
} catch (Exception $e) {
echo '⚠ Exception appear: ' . $e->getMessage();
}
RuntimeException
extendsException
, then you must catchRuntimeException
beforeExceptions
.
In PHP 7.1 you can specify multiple Exception
types with |
char.
try {
// Code
} catch (OutOfBoundsException | LogicException $e) {
echo '⚠ Exception appear: ' . $e->getMessage();
}
⚠ It's very important to correctly choose the Exception
type to preserve error handler consistency.
Need to know
Most of LogicException
usually leads to a code correction.
To catch LogicException
is going to show an error page and log information in order to inform the developer.
RuntimeException
represents errors that appear during the execution (invalide data, data source error).
You can catch RuntimeException
in order to execute an alternative code for finishing the process correctly.
ℹ️ You must have an exception handler to render a nice error page to visitors. The second purpose is to avoid any leaking informations (file path, stack trace, error message...) Best practice: Don't let the exception break the website.
set_exception_handler(function($exception){
echo 'Error appear. Retry in a moment.';
// log($exception->getMessage());
// developer email
});
the error code is an integer
which can be used to codify/identify the error.
You can show the error code instead of the message of the real
Exception
to the visitor, and prevent him to potentially be confronted to sensitive data.
It's very useful to create your custom Exception
class. They are more accurate and can carry extra data (text, object, array...) to the error process.
class MyObject
{
public $content;
}
class MyObjectException extends RuntimeException
{
/**
* @var MyObject
*/
private $myObject;
public function __construct(MyObject $myObject, $message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->myObject = $myObject;
}
/**
* @return MyObject
*/
public function getMyObject()
{
return $this->myObject;
}
}
When
MyObjectException
is caught, you can get backMyObject
with the methodgetMyObject()
You can use this object to run alternative processes, that will provide you more information than with the regularException
.
Sometime we need to trace/log what's going wrong.
In this case we will have to catch the Exception
, then do an alternative process (log, email, ...) and rethrow this exception.
try {
// content update
} catch (Exception $e) {
// log('Update failed.');
throw $e;
}
Here's a concrete example:
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
class UserFactory implements LoggerAwareInterface
{
use LoggerAwareTrait;
private $passwordGenerator;
public function construct(PasswordGeneratorInterface $passwordGenerator)
{
$this->passwordGenerator = $passwordGenerator;
$this->logger = new NullLogger();
}
public function create()
{
try {
$user = new User();
$password = $this->passwordGenerator->generatePassword();
$user->setPassword($password);
return $user;
} catch (Exception $exception) {
$this->logger->error('Error appear during user creation. Reason: ' . $exception->getMessage());
throw $exception;
}
}
}
interface PasswordGeneratorInterface
{
public function generatePassword();
}
We are logging a message and letting the
Exception
bubble up.
Wrap an Exception
is very useful to create a nice stack trace and delegate the exception handling to the main exception handler.
try {
// content update
} catch (RuntimeException $exception) {
throw new UpdateContentException('Content update failed.', 0, $exception);
}
class UpdateContentException extends RuntimeException {}
During the content update, the exception type doesn't matter and will always return an
UpdateContentException
. If you catch theUpdateContentException
, you can access to all previous exceptions with thegetPrevious()
method.
Here's a concrete example:
class UserFactory
{
private $passwordGenerator;
public function construct(PasswordGeneratorInterface $passwordGenerator)
{
$this->passwordGenerator = $passwordGenerator;
}
public function create()
{
try {
$user = new User();
$password = $this->passwordGenerator->generatePassword();
$user->setPassword($password);
return $user;
} catch (Exception $exception) {
throw new UserFactoryException('Error appear during user creation.', 0, $exception);
}
}
}
class UserFactoryException extends RuntimeException {}
interface PasswordGeneratorInterface
{
public function generatePassword();
}
UserFactory::create() always tyhrows an
UserFactoryException
. The first information we need to know is what is going wrong? -> We can't create a user. Why ? -> exception->getPrevious() Layer separation is preserved.
We have seen how to throw and catch exceptions, and even how to customize them with PHP. We have also seen how more advanced exception usages such as rethrow and wrap, in order to have a better control when something wrong happens.
Errors exists in our code, in external library, or when hardware fails. that's why understanding Throwable is essential to handle errors cleverly.
Pros
Cons
catch
/set_exception_handler
to avoid leaking informationsAuthor(s)
Anthony MOUTTE
_Développeur / Concepteur @ ElevenLabs_🚀 je suis très intéressé par la recherche et développement ainsi que les bonnes pratiques.
You wanna know more about something in particular?
Let's plan a meeting!
Our experts answer all your questions.
Contact usDiscover other content about the same topic
You work with Symfony, but the concept of dependency injection is a little blurry for you? Find out how to take advantage of the component reading this article.
Are you suffering from domain anemia? Let's look at what an anemic domain model is and how things can change.
How to deploy PHP applications to AWS Lambda with Bref