abstract PHP FTW!, or What I have learnt from "PHP Objects, Patterns and Principles"

I’m re-reading PHP Objects, Patterns and Principles (2nd Edition) to see if I skipped over something the first time through, but I thought I’d share something that I picked up.

I use Exceptions quite extensively in UHU4, but they’re not very elegant and tend to duplicate a lot of logic. One of the topics covered in PHP Objects, Patterns and Principles is that of abstract classes, and they’re one of the things I’m incorporating into UHU5 as a means of learning-by-implementation towards my PHP Certification studies. Below is a geeky snippet that I’ve developed through things I picked up from the book:

$error = (strlen($this->getMessage()) > 0) ? ':' . sprintf(call_user_func_array(array(get_class($this),'getExtendedMessage'),array($this->getCode())),$this->getMessage()) : '';

For those of you not familiar with PHP, and even those who are this may look rather confusing (as convulted as Perl were Gwyn’s words :-P). I’ll break it down piece by piece to further explain it.

The snippet is the first line of the final public function __toString() method that overrides the __toString() method of the built-in Exception class. __toString() is as class method that is executed when an object is typecast to string, either through echo or die() etc, so in this instance it’s being used to output the user-friendly error message.

Ternary Operator

The snippet uses the ternary operator, which is an “elegant” way of doing an if-else loop for a variable assignment. Without a ternary operator, you’d have something like:

$foo = null; if($bar){ $foo = 'bar'; }else{ $foo = 'baz'; }

Ternary operators allow us to do: $foo = ($bar) ? 'bar' : 'baz'; which cuts down on the number of lines involved.

What my snippet does is check if there has been a message assigned to the Exception object by checking the length of the output of getMessage(), setting $error to an empty string : ''; if the length of the message is 0. In cases where a message has been assigned, we run the sprintf() function on the confusing chunk in the middle.

get_class($this)

The get_class() function returns the name of the encapsulating class. When passed an object instance, it returns the name of the instantiated class (since one class can extend another). Since my Exception class is an abstract extension of the built-in Exception class, we want to make sure the code looks up the correct class, so the $this variable is passed to get_class(). For those not familiar with PHP, when the variable “$this” is used in an object context, it allows an object to refer to itself.

call_user_func_array()

If you’re familiar with WordPress plugin development, you’ll be familiar with this function without realising it. From the WordPress Codex: add_action ( 'hook_name', 'your_function_name', [priority], [accepted_args] );. From the PHP documentation: call_user_func_array ( callback $function , array $param_arr ).

call_user_func_array() allows for the dynamic execution of functions at runtime, as opposed to function calls being hard-coded, or executed via a switch statement.

getExtendedMessage

Combined with get_class($this), call_user_func_array() allows us to call the (abstracted) method getExtendedMessage(). In class uhu_exception, the getExtendedMessage() method is defined as abstract public static function getExtendedMessage($code=null);, containing no implementation beyond the parameters, which forces extending classes to implement it- on pain of parsing errors :-P

As shown from the abstract definition, getExtendedMessage() accepts one parameter, the error code. This is an optional parameter, so it is defined with a default value of null. From the code snippet, although it may not be immediately obvious, we pass this to getExtendedMessage() by setting the $param_arr parameter of call_user_func_array() as array($this->getCode()).

Implementation

4fbac3fd838b631_

The example class, foo_exception extends the abstract class uhu_exception. Since getExtendedMessage() is defined as abstract, we are forced to implement it in foo_exception (__construct() and __toString() don’t need to be re-implemented, so I’ve defined them as class foo_exception extends uhu_exception
{
public static function getExtendedMessage($code=null)
{
switch($code)
{
case 100:
case 101:
case 102:
return 'Error type 1: %s';
break;
default:
return '%s';
break;
}
}
}
btw)

When a foo_exception is thrown then subsequently caught, then converted to string, the final method is invoked, and in the context of foo_exception, the snippet I pasted effectively calls __toString.

foo_exception::getExtendedMessage($this->getCode());

This example would output something along the lines of

exception-test.php had an error on line 60
.

If an error message is passed to foo_exception, try
{
throw new foo_exception();
}
catch(foo_exception $e)
{
die((string)$e);
}
the output would say something along the lines of

exception-test.php had an error on line 60:foo
.

Since the switch statement in throw new foo_exception('foo'); contains a default case, the $error variable in the snippet would return only that there had been an error if no message had been assigned to the thrown exception (see foo_exception::getExtendedMessage() in the snippet), but if a message has been assigned without an error code, the : '' statement returns switch. Since the snippet uses '%s', this effectively makes the function return only the error message, which isn’t particularly useful.

When a supported error code is passed (in the example, 100, 101, 102), the sprintf() statement returns something a little more interesting, so when switch is thrown instead, throw new foo_exception('foo',100); is executed, which returns foo_exception::getExtendedMessage(100), giving an output message of

exception-test.php had an error on line 60:Error type 1: foo
.

More creative things can be done with the 'Error type 1: %s' strings returned by sprintf(), but the basic idea behind using a getExtendedMessage statement is to avoid duplicating the same sub-string of an error message over and over again, e.g. switch, throw new foo_exception('Error type 1: foo',100);, throw new foo_exception('Error type 1: bar',101);. Since the

Error type 1: 
segment of the error string never changes, including it as part of a throw new foo_exception('Error type 1: baz',102); statement for multiple switchs makes it simpler to read through, and easier to update or translate at a later date.

I’m aware I suck at explaining things in a non-technical manner, so if anyone has any questions or suggestions for clarifying the example, please leave a comment :-)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>