Your code can throw exceptions manually whenever you think it necessary.
For example, take the
setId
method from the Unique
trait. Thanks to type hinting, we are
enforcing the ID to be a numeric one, but that is as far as it
goes. What would happen if someone tries to set an ID that is a
negative number? The code right now allows it to go through, but
depending on your preferences, you would like to avoid it. That
would be a good place for an exception to happen. Let's see how we
would add this check and consequent exception:
public function setId($id) {
if ($id < 0) {
throw new \Exception('Id cannot be negative.');
}
if (empty($id)) {
$this->id = ++self::$lastId;
} else {
$this->id = $id;
if ($id > self::$lastId) {
self::$lastId = $id;
}
}
}
As you can see, exceptions are objects of the
class exception. Remember adding the backslash to the name of the
class, unless you want to include it with use
Exception;
at the top of the file. The constructor of the
Exception
class takes some optional
arguments, the first one of them being the message of the
exception. Instances of the class Exception
do nothing by themselves; they have to be
thrown in order to be noticed by the program.
Let's try forcing our program to throw this
exception. In order to do that, let's try to create a customer with
a negative ID. In your init.php
file,
add the following:
$basic = new Basic(-1, "name", "surname", "email");
If you try it now in your browser, PHP will throw a fatal error saying that there was an uncaught exception, which is the expected behavior. For PHP, an exception is something from what it cannot recover, so it will stop execution. That is far from ideal, as you would like to just display an error message to the user, and let them try again.
You can—and should—capture exceptions using the try…catch
blocks. You insert the code that might
throw an exception in the try
block and
if an exception happens, PHP will jump to the catch
block. Let's see how it works:
public function setId(int $id) { try { if ($id < 0) { throw new Exception('Id cannot be negative.'); } if (empty($id)) { $this->id = ++self::$lastId; } else { $this->id = $id; if ($id > self::$lastId) { self::$lastId = $id; } } } catch (Exception $e) { echo $e->getMessage(); } }
If we test the last code snippet in our
browser, we will see the message printed from the catch
block. Calling the getMessage
method on an exception instance will give
us the message—the first argument when creating the object. But
remember that the argument of the constructor is optional; so, do
not rely on the message of the exception too much if you are not
sure how it is generated, as it might be empty.
Note that after the exception is thrown,
nothing else inside the try
block is
executed; PHP goes straight to the catch
block. Additionally, the block gets an argument, which is the
exception thrown. Here, type hinting is mandatory—you will see why
very soon. Naming the argument as $e
is
a widely used convention, even though it is not a good practice to
use poor descriptive names for variables.
Being a bit critical, so far, there is not any
real advantage to be seen in using exceptions in this example. A
simple if…else
block would do exactly
the same job, right? But the real power of exceptions lies in the
ability to be propagated across methods. That is, the exception
thrown on the setId
method, if not
captured, will be propagated to wherever the method was invoked,
allowing us to capture it there. This is very useful, as different
places in the code might want to handle the exception in a
different way. To see how this is done, let's remove the try…catch
inserted in setId
, and place the
following piece of code in your
init.php
file, instead:
try { $basic = new Basic(-1, "name", "surname", "email"); } catch (Exception $e) { echo 'Something happened when creating the basic customer: ' . $e->getMessage(); }
The preceding example shows how useful it is to catch propagated exceptions: we can be more specific of what happens, as we know what the user was trying to do when the exception was thrown. In this case, we know that we were trying to create the customer, but this exception might have been thrown when trying to update the ID of an existing customer, which would need a different error message.