Php Exception Router Implementation
Essentially, I want an exception catcher that will let me declare errors in my library, so that other developers get meaningful error reports, rather than the generic ones provided by PHP. I wrote about it here, but I've thought about it more since then.
Use Case
RedbeanPhp sometimes throws an error with the message Call to a member function find() on null
, which means no database has been connected to redbean. I believe its triggered by the self::$finder->find()
call in this method:
public static function find( $type, $sql = NULL, $bindings = array(), $snippet = NULL )
{
if ( $snippet !== NULL ) self::$writer->setSQLSelectSnippet( $snippet );
return self::$finder->find( $type, $sql, $bindings );
}
This is okay, anybody can follow the stack trace, dig into redbean a little, and go "OH I didn't connect the DB.". But its burdensome.
Simple, cumbersome solution
An alternate would be to rewrite the return
line with a try/catch that adds details like so:
try {
return self::$finder->find( $type, $sql, $bindings );
} catch (\Error $e){
echo "\n\nYou must connect the database.\n";
throw $e;
}
But try/catch
likely adds overhead (something I should benchmark), and its a lot of boilerplate. There will be many calls to self::$finder->find()
across the codebase, and now thats QUITE a lot to manage.
Proposed Exception Catcher
There's one consistent thing here: The message will always be Call to a member function find() on null
, which always means "you need to connect the databse". There are many ways to implement this, but here's my current idea:
<?php
class RedbeanErrors extends \Tlf\ExceptionRouter {
protected $errors = [
0 => [
'message'=>'Call to a member function find() on null',
'report'=>'You need to call `::setup()` to connect the database. See https://redbeanphp.com/index.php?p=/connection',
],
1 => [ /** another exception I specifically handle in my redbean wrapper library RDB */
'message'=>'Array may only contain OODBBeans',
'report'=>'You added a raw value to an ownParamList[]. You MUST only add beans to these.'
]
];
}
And that RedbeanErrors
class would define several other errors. Traits could be used to combine multiple lists of errors if this one array grows taller than a giraffe.
How to use
The user of Redbean could wrap their entire program in a try/catch and call the exception catcher. Example:
require_once(__DIR__.'/vendor/autoload.php');
/** this responds to a request, using a framework I built */
try {
$liaison = new \Liaison();
$package = new \Lia\Package(__DIR__.'/MyWebsite/');
// R::setup();
$liaison->deliver(); // responds to $_SERVER['REQUEST_URI']
} catch (\Throwable $t){
\RedbeanErrors::throw($t);
}
This will fail because OOPS, the ::setup()
line is commented out. The \Tlf\ExceptionRouter
parent class will load the $errors
, and match the messages & give the report
that's much more human friendly, before re-throwing.
Additional Features
We might also want to respond to specific calling classes or something.
<?php
class RedbeanErrors extends \Tlf\ExceptionRouter {
protected $errors = [
0=>[
// just some ideas
'called_from_file'=>'relative-path/to-file.php',
'called_from_class'=>'MyBigErrorFilledClass',
'called_method'=>'OneThatThrowsALot',
'report'=>'Something something, important message'
]
];
protected function onUnhandledError($message, $throwable, array $betterOrganizedErrorInfo){
echo "Woohoo you broke stuff!";
exit;
}
}
We also might care about checking between a line_start
and line_end
if not in a class.
Conclusion, Benefits
RedbeanPhp would have a single class that lists all of its major errors. Users of Redbean would have detailed feedback when they mess up implementation. Less searchable documentation is needed, as error reports can guide new users.
The dependency on \Tlf\ExceptionRouter
could be optional, so Redbean ships with the RedbeanError class, but doesn't require the bloat of my (non-existent) ExceptionRouter
library. For production, this would all be pretty easy to disable, which means benefits to performance & a reduction in your carbon footprint.
Overall, I think this would make development, especially when integrating multiple dependencies, far far easier.
Comment and discuss on twitter
License
This blog post is mine. The ideas and code within it are free to use & claim as your own. If you make it, I hope its free and open source, but that's totally up to you. I also hope you attribute me, but you don't have to.