RSS Feed

Peter Goodman's blog about PHP, Parsing Theory, C++, Functional Programming, Applications,

15 Cool Things About PHP That Most People Overlook

Note: I have followed this post up with 12 Things You Should Dislike About PHP.

I saw this article linked to from Digg and was disappointed by the list. Here's what I think are some of the more interesting features of PHP.

1. Reflection API

PHP5's Reflection API can introspect PHP functions and classes and even allow them to be rebuilt (see this demo)! For example, PHP provides a call_user_func[_array]() function to call functions/methods given a string representation of their name; however, PHP doesn't provide such a function for classes.

class TestCallFunc {
    public function __construct($a, $b) {
        echo $a .' '. $b;
    }
}

//call_user_func_array(array('TestCallFunc', '__construct'), array('hello', 'sir')); // will not work
$class = new ReflectionClass('TestCallFunc');
$class->newInstanceArgs(array('hello', 'sir')); // works

// the above can come in handy for certain cases where you want to unpack an array of values,
// without knowing how many elements are in that array to a classes constructor.

// If one knows the number of arguments of a constructor, or the constructor does not take a
// variable number of arguments, then this will suffice:
$class = 'TestCallFunc';
new $class('hello', 'sir');

Unfortunately the above example hardly does the Reflection API justice. Consider the following use case. Since the Reflection API can figure out almost everything about PHP code it means that it could be used as a strategy for dependency injection. By using smart type hinting within function definitions (see below) and the reflection classes, function dependencies could be figured out on the fly and be satisfied automatically.

2. Ticks

PHP has an unusual statement called declare. At the moment the statement only supports ticks (functions that are executed after every nth process/statement. The implication of tick handlers for timing and testing applications is huge. Imagine being able to chart how long specific processes take as they happen without the burden of having to manually place timing functions within the code! Another possibility would be to pass state to a tick function for analysis. In some cases this would be immensely useful for tracking down bugs or for simply seeing how data is changed throughout the different processes within a program.

3. list(), extract(), and compact()

list() is a function that PHPers learn early and forget about almost as quickly. list() allows for easy unpacking of numeric arrays, eg: array('a', 'b', 'c', ...). List can be used cunningly for simple tasks such as switching the values of two variables:

$a = 'a';
$b = 'b';

list($a, $b) = array($b, $a);

echo $a; // output: b
echo $b; // output: a

List also has a practical application for any function that returns an array. Instead of having to deal with the results as $result[0], $result[1], etc one can use the list() construct to set the values of the array to variables without the hassle of having to do each variable setting manually. Essentially, list() is PHP's way of doing multiple variable assignment.

Unpacking associative arrays is also easily accomplished with extract(). Consider the following simple example:

extract(array('a' => 'a', 'b' => 'b'));
echo $a; // a
echo $b; // b

extract() comes in handy when transferring scope variables around from one place to another is needed. By packing scope variables into an associative array using get_defined_vars() or compact() and then unpacking them somewhere else using extract(), scope variables can be transparently transported. This experiment of mine examples this use of extract().

Another use for extract() is for template engines. Many simple template engines allow their variables to be set in one place using an associative array and accessed as normal variables within the templates. This works simply by extracting that associative array from within the templates.

A special note should is given to compact(), which was mentioned in the comments by Tarique Sani and does the opposite of extract(). It acts in a similar way to get_defined_vars(); however, it gives the programmer more control by allowing them to specify which local variables to pack into an associative array.

4. PHP5 SPL

The PHP5 Standard PHP Library is full of gems. Two of my favorites include the Iterator and IteratorAggregate interfaces.

PHP5's Iterators work transparently with the foreach() construct as if they were arrays. Iterators come in handy when--for example--formatting needs to be applied to a set of data. Be it a MySQL result set or a multidimensional array, Iterators allow for the accessing, formatting it, and return of data all at the same time! The implication is that data is only fetched when it is needed and that any intermediate steps between retrieval and return of data happen transparently.

Sometimes making a class implement Iterator to allow a class to be iterated is bulky. This problem is solved by implementing the IteratorAggregate interface. It works is by defining a getIterator() function within a class that returns an iterator. If you're a Python user, getIterator() is tantamount __iter__().

Two other nifty interfaces in the SPL are ArrayAccess and Countable. Consider the following code:

class Foo implements ArrayAccess, Countable {
    private $vars = array();
    
    // ArrayAccess
    public function offsetGet($key) {
        $ret = NULL;
        if($this->offsetExists($key))
            $ret = $this->vars[$key];
        return $ret;
    }
    public function offsetSet($key, $val) {
        $this->vars[$key] = $val;
    }
    public function offsetUnset($key) {
        unset($this->vars[$key]);
    }
    public function offsetExists($key) {
        return isset($this->vars[$key]);
    }
    
    // Countable
    public function count() {
        return count($this->vars);
    }
}

$foo = new Foo;
$foo['bar'] = 'baz'; // calls Foo::offsetSet()
echo $foo['bar']; // output: baz, calls Foo::offsetGet()

echo count($foo); // output: 1, calls Foo::count()

As is clear, the Foo class acts in many respects like an array. This functionality is desirable when the convenience of array-like access to a class and the power of class methods are used to enhance productivity and functionality. (As a side note, check out ArrayObject.)

When do any of the SPL classes come in handy? Consider the following object-oriented interface to a MySQL database: iterators are used to fetch the results only when they are needed; Countable allows calling of the native count() function on that query result instead of custom and unintuitive numRows() function; and ArrayAccess is used to represent each row from the database where row objects act like arrays for convenience but can implement more advanced business logic through extension.

The SPL classes allow programmers to interact with their classes more natively and intuitively than ever before. Implementing the different interfaces and classes from the SPL into code allows for transparent interaction with different data types in the same way.

5. __autoload()

__autoload is called transparently by PHP when a class that doesn't exist is instantiated. Autoload will take in a class name and then go and try and find the file that the class is in, include it, and let the program continue running its course.

Although this is a handy function I suggest that it not be implemented directly. The reason why I say don't use __autoload() is because it has no namespace. Simply put, if two (or more) applications are running at the same time and share common files that both happen to define the __autoload() function then there will be errors. Instead, please check out spl_autoload() and spl_autoload_register().

6. Type Hinting

Type hinting is used to make sure that the information being passed into specific functions complies with an expected data type. Unfortunately type hinting only works for objects and arrays, but it's still extremely nifty! Note: as the following example shows, PHP will error when an unexpected data format is passed to the function. The following is an example of type hinting in action for objects and for arrays:

function test(array $a, stdclass $b) {
    echo 'yay';
}

test(array(), new stdclass); // works
test('a', 'b'); // results in an error

7. Abstract Classes and Iterfaces

When classes should follow specific contracts interfaces and abstract classes should be used. Interfaces define the required functions and function arguments for all implementing classes. Abstract classes are similar in that respect; however, they also allow for functions to be regularly defined (with bodies).

Note: interfaces nor abstract classes can be instantiated directly. Interfaces need to be implemented (PHP supports multiple interfaces for classes) and abstract classes must be extended.

Obvious use cases for interfaces and abstract classes are for abstraction layers where its useful to define a single interface in one place and let extending classes implement the data-specific logic on their own. Simple examples to this would the database abstraction layers, such as ADOdb.

8. "static" keyword

This is more for php4 people and is really a hack. The most common use of the static keyword from within a function is to implement singleton factories for classes. With improvements in PHPs object-oriented model in PHP5, the static keyword is not as useful because classes allow for static methods and proper function visibilities.

function increment() {
    static $count;
    if($count === NULL) {
        $count = 0;
    }
    return ++$count;
}
echo increment(); // 1
echo increment(); // 2
echo increment(); // 3
// ...

9. === and !==

These are extremely useful and most PHPers can't tell the difference between == and ===. Essentially, == looks for equality, and by that PHP will generally try to coerce data into similar formats, eg: 1 == '1' (true), whereas === looks for identity: 1 === '1' (false). The usefulness of these operators should be immediately recognized for common functions such as strpos(). Since zero in PHP is analogous to FALSE it means that without this operator there would be no way to tell from the result of strpos() if something is at the beginning of a string or if strpos() failed to find anything. Obviously this has many applications elsewhere where returning zero is not equivalent to FALSE.

10. Variable Assignment from within Conditional Statements

This is another fun trick that can also be a pain in the ass. Mistakingly doing if($result = '1') instead of if($result == '1') often happens and is a difficult bug to trace. Interestingly, this annoyance can also come in handy! Try this: check if a strpos() failed or not and get the proper result of it all from within an if() statement. If you don't know how it would work then take a look at the following code:

if(FALSE !== ($pos = strpos('aaaa', 'a'))) {
    echo $pos; // echo's 0
}

11. PHP's Magic Functions

They definitely don't compare to what Python offers but they can be pretty nifty. Couple these functions with the SPL and some pretty cool classes can be made.

__call()

The __call() magic function allows for a class to call functions that it doesn't necessarily have. For example, if there are a lot of redundant functions in a class that work in very similar ways but still require a slight amount of custom logic then maybe __call() would allow for a better implementation. Consider an associative array where the keys represent a white list of functions that __call should work with and their values would somehow allow __call to implement the slight differences between the functions.

If that example seems unlikely then let me detail two ways that I've used __call.

The first way was implementing filters for different MySQL data types. Data for VARCHAR, INT, CHAR, TEXT, et al. can all be handled in very similar ways but require slightly different type casts and regular expressions checks. Well, storing the unique checks in an array with function names for each data type dramatically reduced the amount of redundant code I had to write and made the system more maintainable for when I needed to add more data types to the list.

The second way that I implemented __call() was for credit card processing APIs. I recently made a library to interact with the Authorize.net API and it was expected of me that in the event that the processor was changed that the only thing that would need to be changed is the call to instantiate the appropriate class. If anyone has worked with payment processors before then they will know that the data they accept can be crytpic (eg: x_login, x_customer_cc). To make things simple, I used call to map an associative array of keys (function names) to the processor-specific variables that they modified. Instead of setting an x_login field, I could do: $AIM->login().

__get(), __set(), __isset(), __unset()

These act similarly to ArrayAccesses offsetGet, offsetSet, etc etc, except that these functions allow you to access non-existent instance variables.

__toString()

What happens when you try to echo a class instance? Normally the output will be the unhelpful Object id #1 (or another number). This can be changed though! Changing what is returned from an echo is as simple as defining a custom __toString() method and giving it a custom return value.

12. __halt_compiler(): Halt the Compiler!

Ever wanted to make the installer for a program and the entire program to be installed fit into one file? This can actually be done by using __halt_compiler()! Check it out and make sure to look up how applications such as Phar and FUDForum take advantage of it.

I was asked how this was different from exit() so I will explain. Imagine having a PHP installer script at the top of a file and then having a compressed version of, for example, a forum (in the case of FUDForum) in the rest of the file. When __halt_compiler() is called, PHP literally stops parsing and compiling past that point. That means that the installer can open itself up, look for everything after the __halt_compiler() call, unzip it, and then store it on the server.

13. Variable Composition

Just see this experiment for more interesting implementations, but otherwise the following is possible:

${'a' . 'b'} = 'c';
echo $ab; // c

14. Chaining Method Calls

If you're a fan of jQuery then you will love Javascript's ability to chain methods. PHP5 can also do this! For example: (take note of return $this)

class Foo {
    public function bar() {
        return $this;
    }
    public function baz() {
        return $this;
    }
}

$foo = new Foo;
$foo->bar()->baz()->bar()->.... // these two functions can continue to be chained

The above application doesn't show any practical use for chaining so I encourage people to look at how the CodeIgniter PHP framework uses method chaining for database query building.

15. preg_split

Have you ever use explode() and then end up looping through the results and picking out the empty ones? Isn't that annoying? By using preg_split this tedious process can be avoided:

// instead of...
$str = "a,b,,c,d"
explode(",", $str); // array('a','b','','c','d')

// do...
preg_split("~,~", $str, -1, PREG_SPLIT_NO_EMPTY); 

// return: array('a','b','c','d')

The above example might not seem all that useful so consider bb code parsers. One of the major problems with parsing bb code is that most of the bbcode to html translators don't fix improper closing tags, or even check for them! Generally speaking, most bb code parsers will to a flat preg_replace for each of the tags.

A better approach to parsing bb code is to split the text up into parts that are text and parts that are bbcode-tag-like, eg: ~[([^]]*)]~. By doing this one can go through the split up results and parse the bbcode using a stack. This allows for both proper checking of bbcode opening and closing tags along with the ability to implement a generalized solution so that adding bbcodes isn't about making a new preg_replace but rather making a class/function that knows how to correctly handle the opening and closing tags.

In one of my previous posts I use the above mentioned technique to parse HTML for special XML-like template tags.


Comments


Comment